Added from_to test case
This commit is contained in:
@@ -195,6 +195,7 @@ types:
|
|||||||
generate_condition_statements: !optional bool
|
generate_condition_statements: !optional bool
|
||||||
participants_order: !optional [string]
|
participants_order: !optional [string]
|
||||||
start_from: !optional [source_location_t]
|
start_from: !optional [source_location_t]
|
||||||
|
from_to: !optional [[source_location_t]]
|
||||||
package_diagram_t:
|
package_diagram_t:
|
||||||
type: !variant [package]
|
type: !variant [package]
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -59,14 +59,7 @@ void generator::generate_call(const message &m, std::ostream &ostr) const
|
|||||||
std::string message;
|
std::string message;
|
||||||
|
|
||||||
model::function::message_render_mode render_mode =
|
model::function::message_render_mode render_mode =
|
||||||
model::function::message_render_mode::full;
|
select_method_arguments_render_mode();
|
||||||
|
|
||||||
if (config().generate_method_arguments() ==
|
|
||||||
config::method_arguments::abbreviated)
|
|
||||||
render_mode = model::function::message_render_mode::abbreviated;
|
|
||||||
else if (config().generate_method_arguments() ==
|
|
||||||
config::method_arguments::none)
|
|
||||||
render_mode = model::function::message_render_mode::no_arguments;
|
|
||||||
|
|
||||||
if (to.value().type_name() == "method") {
|
if (to.value().type_name() == "method") {
|
||||||
const auto &f = dynamic_cast<const model::method &>(to.value());
|
const auto &f = dynamic_cast<const model::method &>(to.value());
|
||||||
@@ -415,7 +408,7 @@ void generator::generate_diagram(std::ostream &ostr) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &ft : m_config.from_to()) {
|
for (const auto &ft : config().from_to()) {
|
||||||
// First, find the sequence of activities from 'from' location
|
// First, find the sequence of activities from 'from' location
|
||||||
// to 'to' location
|
// to 'to' location
|
||||||
assert(ft.size() == 2);
|
assert(ft.size() == 2);
|
||||||
@@ -423,52 +416,156 @@ void generator::generate_diagram(std::ostream &ostr) const
|
|||||||
const auto &from_location = ft.front();
|
const auto &from_location = ft.front();
|
||||||
const auto &to_location = ft.back();
|
const auto &to_location = ft.back();
|
||||||
|
|
||||||
if (from_location.location_type == location_t::function) {
|
if ((from_location.location_type == location_t::function) &&
|
||||||
|
(to_location.location_type == location_t::function)) {
|
||||||
common::model::diagram_element::id_t from_activity{0};
|
common::model::diagram_element::id_t from_activity{0};
|
||||||
common::model::diagram_element::id_t to_activity{0};
|
common::model::diagram_element::id_t to_activity{0};
|
||||||
|
|
||||||
for (const auto &[k, v] : m_model.sequences()) {
|
for (const auto &[k, v] : model().sequences()) {
|
||||||
const auto &caller = *m_model.participants().at(v.from());
|
const auto &caller = *model().participants().at(v.from());
|
||||||
std::string vfrom = caller.full_name(false);
|
std::string vfrom = caller.full_name(false);
|
||||||
if (vfrom == from_location.location) {
|
if (vfrom == from_location.location) {
|
||||||
LOG_DBG("Found sequence diagram start point: {}", k);
|
LOG_DBG("Found sequence diagram start point '{}': {}",
|
||||||
|
vfrom, k);
|
||||||
from_activity = k;
|
from_activity = k;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (from_activity == 0) {
|
if (from_activity == 0) {
|
||||||
LOG_WARN("Failed to find participant with {} for start_from "
|
LOG_WARN("Failed to find 'from' participant {} for start_from "
|
||||||
"condition",
|
"condition",
|
||||||
from_location.location);
|
from_location.location);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &[k, v] : m_model.sequences()) {
|
for (const auto &[k, v] : model().sequences()) {
|
||||||
const auto &caller = *m_model.participants().at(v.from());
|
for (const auto &m : v.messages()) {
|
||||||
std::string vfrom = caller.full_name(false);
|
if (m.type() != message_t::kCall)
|
||||||
if (vfrom == to_location.location) {
|
continue;
|
||||||
LOG_DBG("Found sequence diagram end point: {}", k);
|
const auto &callee = *model().participants().at(m.to());
|
||||||
to_activity = k;
|
std::string vto = callee.full_name(false);
|
||||||
break;
|
if (vto == to_location.location) {
|
||||||
|
LOG_DBG("Found sequence diagram end point '{}': {}",
|
||||||
|
vto, m.to());
|
||||||
|
to_activity = m.to();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (to_activity == 0) {
|
if (to_activity == 0) {
|
||||||
LOG_WARN("Failed to find participant with {} for from_to "
|
LOG_WARN("Failed to find 'to' participant {} for from_to "
|
||||||
"condition",
|
"condition",
|
||||||
to_location.location);
|
to_location.location);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
call_chain_t activity_path;
|
// Message (call) chains matching the specified from_to condition
|
||||||
activity_path.push_back(from_activity);
|
std::vector<message_chain_t> message_chains;
|
||||||
|
|
||||||
auto found = search_path_to(activity_path, to_activity);
|
// First find all 'to_activity' call targets in the sequences, i.e.
|
||||||
|
// all messages pointing to the final 'to_activity' activity
|
||||||
|
for (const auto &[k, v] : model().sequences()) {
|
||||||
|
for (const auto &m : v.messages()) {
|
||||||
|
if (m.type() != message_t::kCall)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (m.to() == to_activity) {
|
||||||
|
message_chains.push_back(message_chain_t{});
|
||||||
|
message_chains.back().push_back(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<int, model::message> calls_to_current_chain;
|
||||||
|
std::map<int, message_chain_t> current_chain;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
bool added_message_to_some_chain{false};
|
||||||
|
// If target of current message matches any of the
|
||||||
|
// 'from' constraints in the last messages in
|
||||||
|
// current chains - append
|
||||||
|
|
||||||
|
if (!calls_to_current_chain.empty()) {
|
||||||
|
for (auto &[message_chain_index, message] :
|
||||||
|
calls_to_current_chain) {
|
||||||
|
message_chains.push_back(
|
||||||
|
current_chain[message_chain_index]);
|
||||||
|
message_chains.back().push_back(std::move(
|
||||||
|
calls_to_current_chain[message_chain_index]));
|
||||||
|
}
|
||||||
|
calls_to_current_chain.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i = 0U; i < message_chains.size(); i++) {
|
||||||
|
auto &mc = message_chains[i];
|
||||||
|
current_chain[i] = mc;
|
||||||
|
for (const auto &[k, v] : model().sequences()) {
|
||||||
|
|
||||||
|
for (const auto &m : v.messages()) {
|
||||||
|
if (m.type() != message_t::kCall)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Ignore recursive calls
|
||||||
|
if (m.to() == m.from()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.to() == mc.back().from()) {
|
||||||
|
calls_to_current_chain[i] = m;
|
||||||
|
added_message_to_some_chain = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are more than one call to the current chain,
|
||||||
|
// duplicate it as many times as there are calls - 1
|
||||||
|
if (calls_to_current_chain.size() >= 1) {
|
||||||
|
mc.push_back(calls_to_current_chain[i]);
|
||||||
|
calls_to_current_chain.erase(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is nothing more to find
|
||||||
|
if (!added_message_to_some_chain)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Reverse the message chains order (they were added starting from
|
||||||
|
// the destination activity)
|
||||||
|
for (auto &mc : message_chains)
|
||||||
|
std::reverse(mc.begin(), mc.end());
|
||||||
|
|
||||||
|
// Remove identical chains
|
||||||
|
std::unordered_set<message_chain_t> message_chains_unique{
|
||||||
|
message_chains.begin(), message_chains.end()};
|
||||||
|
|
||||||
|
auto idx{0U};
|
||||||
|
for (const auto &mc : message_chains_unique) {
|
||||||
|
if (mc.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (mc.front().from() != from_activity)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (const auto &m : mc) {
|
||||||
|
generate_call(m, ostr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx++ < message_chains_unique.size() - 1)
|
||||||
|
ostr << "====\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: Add support for other sequence start location types
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &sf : m_config.start_from()) {
|
for (const auto &sf : config().start_from()) {
|
||||||
if (sf.location_type == location_t::function) {
|
if (sf.location_type == location_t::function) {
|
||||||
common::model::diagram_element::id_t start_from{0};
|
common::model::diagram_element::id_t start_from{0};
|
||||||
for (const auto &[k, v] : model().sequences()) {
|
for (const auto &[k, v] : model().sequences()) {
|
||||||
@@ -507,15 +604,7 @@ void generator::generate_diagram(std::ostream &ostr) const
|
|||||||
std::string from_alias = generate_alias(from.value());
|
std::string from_alias = generate_alias(from.value());
|
||||||
|
|
||||||
model::function::message_render_mode render_mode =
|
model::function::message_render_mode render_mode =
|
||||||
model::function::message_render_mode::full;
|
select_method_arguments_render_mode();
|
||||||
|
|
||||||
if (config().generate_method_arguments() ==
|
|
||||||
config::method_arguments::abbreviated)
|
|
||||||
render_mode = model::function::message_render_mode::abbreviated;
|
|
||||||
else if (config().generate_method_arguments() ==
|
|
||||||
config::method_arguments::none)
|
|
||||||
render_mode =
|
|
||||||
model::function::message_render_mode::no_arguments;
|
|
||||||
|
|
||||||
// For methods or functions in diagrams where they are combined into
|
// For methods or functions in diagrams where they are combined into
|
||||||
// file participants, we need to add an 'entry' point call to know
|
// file participants, we need to add an 'entry' point call to know
|
||||||
@@ -556,4 +645,17 @@ void generator::generate_diagram(std::ostream &ostr) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model::function::message_render_mode
|
||||||
|
generator::select_method_arguments_render_mode() const
|
||||||
|
{
|
||||||
|
if (config().generate_method_arguments() ==
|
||||||
|
config::method_arguments::abbreviated)
|
||||||
|
return model::function::message_render_mode::abbreviated;
|
||||||
|
|
||||||
|
if (config().generate_method_arguments() == config::method_arguments::none)
|
||||||
|
return model::function::message_render_mode::no_arguments;
|
||||||
|
|
||||||
|
return model::function::message_render_mode::full;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace clanguml::sequence_diagram::generators::plantuml
|
} // namespace clanguml::sequence_diagram::generators::plantuml
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ template <typename C, typename D>
|
|||||||
using common_generator =
|
using common_generator =
|
||||||
clanguml::common::generators::plantuml::generator<C, D>;
|
clanguml::common::generators::plantuml::generator<C, D>;
|
||||||
|
|
||||||
using call_chain_t = std::vector<common::model::diagram_element::id_t>;
|
using message_chain_t =
|
||||||
|
std::vector<sequence_diagram::model::message>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sequence diagram PlantUML generator
|
* @brief Sequence diagram PlantUML generator
|
||||||
@@ -142,6 +143,8 @@ private:
|
|||||||
std::string render_name(std::string name) const;
|
std::string render_name(std::string name) const;
|
||||||
|
|
||||||
mutable std::set<common::id_t> generated_participants_;
|
mutable std::set<common::id_t> generated_participants_;
|
||||||
|
model::function::message_render_mode
|
||||||
|
select_method_arguments_render_mode() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace plantuml
|
} // namespace plantuml
|
||||||
|
|||||||
@@ -162,3 +162,33 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
} // namespace clanguml::sequence_diagram::model
|
} // namespace clanguml::sequence_diagram::model
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
|
||||||
|
template <> struct hash<clanguml::sequence_diagram::model::message> {
|
||||||
|
std::size_t operator()(
|
||||||
|
const clanguml::sequence_diagram::model::message &m) const
|
||||||
|
{
|
||||||
|
std::size_t seed = m.from() << 2;
|
||||||
|
seed ^= m.to();
|
||||||
|
seed += std::hash<std::string>{}(m.full_name(true));
|
||||||
|
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct hash<std::vector<clanguml::sequence_diagram::model::message>> {
|
||||||
|
std::size_t operator()(
|
||||||
|
const std::vector<clanguml::sequence_diagram::model::message> &msgs)
|
||||||
|
const
|
||||||
|
{
|
||||||
|
std::size_t seed = msgs.size() << 8;
|
||||||
|
for (const auto &m : msgs) {
|
||||||
|
seed ^= std::hash<clanguml::sequence_diagram::model::message>{}(m);
|
||||||
|
}
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace std
|
||||||
|
|||||||
15
tests/t20034/.clang-uml
Normal file
15
tests/t20034/.clang-uml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
compilation_database_dir: ..
|
||||||
|
output_directory: puml
|
||||||
|
diagrams:
|
||||||
|
t20034_sequence:
|
||||||
|
type: sequence
|
||||||
|
glob:
|
||||||
|
- ../../tests/t20034/t20034.cc
|
||||||
|
include:
|
||||||
|
namespaces:
|
||||||
|
- clanguml::t20034
|
||||||
|
using_namespace:
|
||||||
|
- clanguml::t20034
|
||||||
|
from_to:
|
||||||
|
- [function: "clanguml::t20034::D::d2()",
|
||||||
|
function: "clanguml::t20034::A::a2()"]
|
||||||
55
tests/t20034/t20034.cc
Normal file
55
tests/t20034/t20034.cc
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#include <cstdint>
|
||||||
|
namespace clanguml {
|
||||||
|
namespace t20034 {
|
||||||
|
struct A {
|
||||||
|
void a1() { }
|
||||||
|
void a2() { }
|
||||||
|
void a3() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct B {
|
||||||
|
void b1()
|
||||||
|
{
|
||||||
|
a.a1();
|
||||||
|
a.a2();
|
||||||
|
}
|
||||||
|
void b2() { a.a2(); }
|
||||||
|
void b3() { a.a3(); }
|
||||||
|
|
||||||
|
A a;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct C {
|
||||||
|
void c1() { b.b1(); }
|
||||||
|
void c2() { b.b2(); }
|
||||||
|
void c3()
|
||||||
|
{
|
||||||
|
if (reinterpret_cast<uint64_t>(&b) == 0xbadc0de)
|
||||||
|
c3();
|
||||||
|
else
|
||||||
|
c2();
|
||||||
|
}
|
||||||
|
|
||||||
|
B b;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct D {
|
||||||
|
void d1() { c.c1(); }
|
||||||
|
void d2()
|
||||||
|
{
|
||||||
|
c.c1();
|
||||||
|
a.a2();
|
||||||
|
c.c2();
|
||||||
|
c.c3();
|
||||||
|
a.a2();
|
||||||
|
|
||||||
|
auto l = [this]() { a.a2(); };
|
||||||
|
l();
|
||||||
|
}
|
||||||
|
void d3() { c.c3(); }
|
||||||
|
|
||||||
|
A a;
|
||||||
|
C c;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
63
tests/t20034/test_case.h
Normal file
63
tests/t20034/test_case.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* tests/t20034/test_case.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-2023 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("t20034", "[test-case][sequence]")
|
||||||
|
{
|
||||||
|
auto [config, db] = load_config("t20034");
|
||||||
|
|
||||||
|
auto diagram = config.diagrams["t20034_sequence"];
|
||||||
|
|
||||||
|
REQUIRE(diagram->name == "t20034_sequence");
|
||||||
|
|
||||||
|
auto model = generate_sequence_diagram(*db, diagram);
|
||||||
|
|
||||||
|
REQUIRE(model->name() == "t20034_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("D"), _A("A"), "a2()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("D"), _A("C"), "c3()"));
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("C"), _A("C"), "c2()"));
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("C"), _A("B"), "b2()"));
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2()"));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("D"), _A("C"), "c2()"));
|
||||||
|
REQUIRE_THAT(puml, !HasCall(_A("D"), _A("C"), "c1()"));
|
||||||
|
|
||||||
|
|
||||||
|
save_puml(
|
||||||
|
config.output_directory() + "/" + diagram->name + ".puml", puml);
|
||||||
|
}
|
||||||
|
|
||||||
|
// {
|
||||||
|
// auto j = generate_sequence_json(diagram, *model);
|
||||||
|
//
|
||||||
|
// using namespace json;
|
||||||
|
//
|
||||||
|
// save_json(config.output_directory() + "/" + diagram->name + ".json", j);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -347,6 +347,7 @@ using namespace clanguml::test::matchers;
|
|||||||
#include "t20031/test_case.h"
|
#include "t20031/test_case.h"
|
||||||
#include "t20032/test_case.h"
|
#include "t20032/test_case.h"
|
||||||
#include "t20033/test_case.h"
|
#include "t20033/test_case.h"
|
||||||
|
#include "t20034/test_case.h"
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Package diagram tests
|
/// Package diagram tests
|
||||||
|
|||||||
Reference in New Issue
Block a user