Added option to enable rendering return types in sequence diagrams (fixes #93)

This commit is contained in:
Bartek Kryza
2023-07-04 00:38:42 +02:00
parent d944a2cead
commit 2104d930a8
19 changed files with 274 additions and 29 deletions

15
tests/t20032/.clang-uml Normal file
View File

@@ -0,0 +1,15 @@
compilation_database_dir: ..
output_directory: puml
diagrams:
t20032_sequence:
type: sequence
glob:
- ../../tests/t20032/t20032.cc
include:
namespaces:
- clanguml::t20032
using_namespace:
- clanguml::t20032
generate_return_types: true
start_from:
- function: "clanguml::t20032::tmain(int,char **)"

27
tests/t20032/t20032.cc Normal file
View File

@@ -0,0 +1,27 @@
namespace clanguml {
namespace t20032 {
struct A {
int a1(int i) { return i; }
double a2(double d) { return d; }
const char *a3(const char *s) { return s; }
};
struct B {
int b(int i) { return a.a1(i); }
double b(double d) { return a.a2(d); }
const char *b(const char *s) { return a.a3(s); }
A a;
};
void tmain(int argc, char **argv)
{
B b;
b.b(1);
b.b(2.0);
b.b("three");
}
}
}

79
tests/t20032/test_case.h Normal file
View File

@@ -0,0 +1,79 @@
/**
* tests/t20032/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("t20032", "[test-case][sequence]")
{
auto [config, db] = load_config("t20032");
auto diagram = config.diagrams["t20032_sequence"];
REQUIRE(diagram->name == "t20032_sequence");
auto model = generate_sequence_diagram(*db, diagram);
REQUIRE(model->name() == "t20032_sequence");
REQUIRE(model->name() == "t20032_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(int,char **)"), _A("B"), "b(int)"));
REQUIRE_THAT(
puml, HasResponse(_A("tmain(int,char **)"), _A("B"), "int"));
REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a1(int)"));
REQUIRE_THAT(puml, HasResponse(_A("B"), _A("A"), "int"));
REQUIRE_THAT(
puml, HasCall(_A("tmain(int,char **)"), _A("B"), "b(double)"));
REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2(double)"));
REQUIRE_THAT(puml, HasResponse(_A("B"), _A("A"), "double"));
REQUIRE_THAT(puml,
HasCall(_A("tmain(int,char **)"), _A("B"), "b(const char *)"));
REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a3(const char *)"));
REQUIRE_THAT(puml, HasResponse(_A("B"), _A("A"), "const char *"));
save_puml(
config.output_directory() + "/" + diagram->name + ".puml", puml);
}
{
auto j = generate_sequence_json(diagram, *model);
using namespace json;
std::vector<int> messages = {
FindMessage(j, "tmain(int,char **)", "B", "b(int)", "int"),
FindMessage(j, "B", "A", "a1(int)", "int"),
FindMessage(j, "tmain(int,char **)", "B", "b(double)"),
FindMessage(j, "B", "A", "a2(double)"),
FindMessage(j, "tmain(int,char **)", "B", "b(const char *)"),
FindMessage(j, "B", "A", "a3(const char *)")};
REQUIRE(std::is_sorted(messages.begin(), messages.end()));
save_json(config.output_directory() + "/" + diagram->name + ".json", j);
}
}

View File

@@ -345,6 +345,7 @@ using namespace clanguml::test::matchers;
#include "t20029/test_case.h"
#include "t20030/test_case.h"
#include "t20031/test_case.h"
#include "t20032/test_case.h"
///
/// Package diagram tests

View File

@@ -134,12 +134,15 @@ struct HasCallWithResultMatcher : ContainsMatcher {
template <typename T> class HasCallMatcher : public Catch::MatcherBase<T> {
T m_from, m_to, m_message;
bool m_is_response;
std::string call_pattern, response_pattern;
public:
HasCallMatcher(T from, T to, T message)
HasCallMatcher(T from, T to, T message, bool is_response = false)
: m_from(from)
, m_to{to}
, m_message{message}
, m_is_response{is_response}
{
util::replace_all(m_message, "(", "\\(");
util::replace_all(m_message, ")", "\\)");
@@ -147,15 +150,22 @@ public:
util::replace_all(m_message, "[", "\\[");
util::replace_all(m_message, "]", "\\]");
util::replace_all(m_message, "+", "\\+");
call_pattern = fmt::format("{} -> {} "
"(\\[\\[.*\\]\\] )?: {}",
m_from, m_to, m_message);
response_pattern =
fmt::format("{} --> {} : //{}//", m_from, m_to, m_message);
}
bool match(T const &in) const override
{
std::istringstream fin(in);
std::string line;
std::regex r{fmt::format("{} -> {} "
"(\\[\\[.*\\]\\] )?: {}",
m_from, m_to, m_message)};
std::regex r{m_is_response ? response_pattern : call_pattern};
while (std::getline(fin, line)) {
std::smatch base_match;
std::regex_search(in, base_match, r);
@@ -182,6 +192,13 @@ auto HasCall(std::string const &from, std::string const &to,
return HasCallMatcher(from, to, message);
}
auto HasResponse(std::string const &from, std::string const &to,
std::string const &message,
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
{
return HasCallMatcher(to, from, message, true);
}
auto HasCallInControlCondition(std::string const &from, std::string const &to,
std::string const &message,
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
@@ -955,7 +972,8 @@ bool IsFileParticipant(const nlohmann::json &j, const std::string &name)
namespace detail {
int find_message_nested(const nlohmann::json &j, const std::string &from,
const std::string &to, const std::string &msg, const nlohmann::json &from_p,
const std::string &to, const std::string &msg,
std::optional<std::string> return_type, const nlohmann::json &from_p,
const nlohmann::json &to_p, int &count)
{
const auto &messages = j["messages"];
@@ -965,23 +983,25 @@ int find_message_nested(const nlohmann::json &j, const std::string &from,
for (const auto &m : messages) {
if (m.contains("branches")) {
for (const auto &b : m["branches"]) {
auto nested_res =
find_message_nested(b, from, to, msg, from_p, to_p, count);
auto nested_res = find_message_nested(
b, from, to, msg, return_type, from_p, to_p, count);
if (nested_res >= 0)
return nested_res;
}
}
else if (m.contains("messages")) {
auto nested_res =
find_message_nested(m, from, to, msg, from_p, to_p, count);
auto nested_res = find_message_nested(
m, from, to, msg, return_type, from_p, to_p, count);
if (nested_res >= 0)
return nested_res;
}
else {
if ((m["from"]["participant_id"] == from_p["id"]) &&
(m["to"]["participant_id"] == to_p["id"]) && (m["name"] == msg))
(m["to"]["participant_id"] == to_p["id"]) &&
(m["name"] == msg) &&
(!return_type || m["return_type"] == *return_type))
return count;
count++;
@@ -992,7 +1012,8 @@ int find_message_nested(const nlohmann::json &j, const std::string &from,
}
int find_message_impl(const nlohmann::json &j, const std::string &from,
const std::string &to, const std::string &msg)
const std::string &to, const std::string &msg,
std::optional<std::string> return_type)
{
auto from_p = get_participant(j, from);
@@ -1004,7 +1025,7 @@ int find_message_impl(const nlohmann::json &j, const std::string &from,
int count{0};
auto res = detail::find_message_nested(
sequence_0, from, to, msg, *from_p, *to_p, count);
sequence_0, from, to, msg, return_type, *from_p, *to_p, count);
if (res >= 0)
return res;
@@ -1018,14 +1039,15 @@ int find_message_impl(const nlohmann::json &j, const std::string &from,
int FindMessage(const nlohmann::json &j, const File &from, const File &to,
const std::string &msg)
{
return detail::find_message_impl(j, from.file, to.file, msg);
return detail::find_message_impl(j, from.file, to.file, msg, {});
}
int FindMessage(const nlohmann::json &j, const std::string &from,
const std::string &to, const std::string &msg)
const std::string &to, const std::string &msg,
std::optional<std::string> return_type = {})
{
return detail::find_message_impl(
j, expand_name(j, from), expand_name(j, to), msg);
j, expand_name(j, from), expand_name(j, to), msg, return_type);
}
} // namespace json

View File

@@ -292,6 +292,9 @@ test_cases:
- name: t20031
title: Callee type sequence diagram filter test case
description:
- name: t20032
title: Return type generation option sequence diagram test case
description:
Package diagrams:
- name: t30001
title: Basic package diagram test case

View File

@@ -323,3 +323,20 @@ TEST_CASE("Test config diagram_templates", "[unit-test]")
REQUIRE(cfg.diagram_templates()["main_sequence_tmpl"].type ==
clanguml::common::model::diagram_t::kSequence);
}
TEST_CASE("Test config sequence inherited", "[unit-test]")
{
auto cfg = clanguml::config::load(
"./test_config_data/sequence_inheritable_options.yml");
CHECK(cfg.diagrams.size() == 2);
auto &def = *cfg.diagrams["sequence_default"];
CHECK(def.type() == clanguml::common::model::diagram_t::kSequence);
CHECK(def.combine_free_functions_into_file_participants() == true);
CHECK(def.generate_return_types() == true);
def = *cfg.diagrams["sequence_default2"];
CHECK(def.type() == clanguml::common::model::diagram_t::kSequence);
CHECK(def.combine_free_functions_into_file_participants() == false);
CHECK(def.generate_return_types() == false);
}

View File

@@ -0,0 +1,15 @@
compilation_database_dir: debug
output_directory: output
combine_free_functions_into_file_participants: true
generate_return_types: true
diagrams:
sequence_default:
type: sequence
glob:
- src/*.cc
sequence_default2:
type: sequence
glob:
- src/*.cc
combine_free_functions_into_file_participants: false
generate_return_types: false