Added support for call expressions tracking through lambdas in function arguments (#168)

This commit is contained in:
Bartek Kryza
2024-04-20 23:16:02 +02:00
parent 0fac87c27a
commit 9c07dbfde3
9 changed files with 452 additions and 44 deletions

17
tests/t20044/.clang-uml Normal file
View File

@@ -0,0 +1,17 @@
add_compile_flags:
- -fparse-all-comments
diagrams:
t20044_sequence:
type: sequence
glob:
- t20044.cc
generate_message_comments: true
include:
namespaces:
- clanguml::t20044
exclude:
namespaces:
- clanguml::t20044::detail2
using_namespace: clanguml::t20044
from:
- function: "clanguml::t20044::tmain()"

100
tests/t20044/t20044.cc Normal file
View File

@@ -0,0 +1,100 @@
// #include "include/expected.hpp"
#include <functional>
#include <optional>
#include <thread>
namespace clanguml {
namespace t20044 {
enum class error { OK, FAIL };
namespace detail {
// Trivial std::expected mock-up just for testing calls through lambda
// expressions passed as arguments to methods
template <typename V, typename E> class expected {
private:
std::optional<V> value_;
std::optional<E> error_;
public:
explicit expected(V v)
: value_{std::move(v)}
{
}
explicit expected(E e)
: error_{std::move(e)}
{
}
const auto &value() const { return *value_; }
const auto &error() const { return *error_; }
template <class F> auto and_then(F &&f) &&
{
if (value_)
return f(*value_);
return *this;
}
};
} // namespace detail
namespace detail2 {
template <typename F> void run(F &&f) { f(); }
} // namespace detail2
using result_t = detail::expected<int, error>;
struct A {
auto a() const { }
auto a1() const { return result_t{10}; }
auto a2(int arg) const { return result_t{arg + 1}; }
auto a4(int arg) const { return result_t{arg + 1}; }
void a5() { }
};
auto a3(int arg) { return result_t{arg + 1}; }
struct R {
template <typename F> R(F &&f) { f(); }
};
int tmain()
{
A a;
// Call to template constructor with callable parameter and lambda
// expression as argument
R r([&a]() { a.a(); });
std::function<result_t(const A &, int)> a4_wrapper = &A::a4;
std::function<result_t(int)> a4_wrapper_to_a =
std::bind(a4_wrapper, a, std::placeholders::_1);
// The message to detail2::run() is skipped due to exclude filter, however
// the call to lambda and A::a5() is rendered
// TODO: Add some marker to highlight that this is not a direct call
detail2::run([&]() { a.a5(); });
return a
.a1()
// Call to a template method accepting a callable with lambda expression
// as argument, fully tracked showing method's activity and
.and_then([&](auto &&arg) { return a.a2(arg); })
// TODO: Call to a method accepting a callable with function pointer
// as argument
.and_then(a3)
// TODO: Call to a method accepting a callable with std::function as
// argument
.and_then(a4_wrapper_to_a)
.value();
}
}
}

91
tests/t20044/test_case.h Normal file
View File

@@ -0,0 +1,91 @@
/**
* tests/t20044/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("t20044", "[test-case][sequence]")
{
auto [config, db] = load_config("t20044");
auto diagram = config.diagrams["t20044_sequence"];
REQUIRE(diagram->name == "t20044_sequence");
auto model = generate_sequence_diagram(*db, diagram);
REQUIRE(model->name() == "t20044_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("R"), "R((lambda at t20044.cc:74:9) &&)"));
REQUIRE_THAT(src,
HasCall(_A("R"), _A("tmain()::(lambda t20044.cc:74:9)"),
"operator()()"));
REQUIRE_THAT(src,
HasCall(_A("tmain()::(lambda t20044.cc:74:9)"), _A("A"), "a()"));
REQUIRE_THAT(src,
HasCall(_A("tmain()"), _A("tmain()::(lambda t20044.cc:84:18)"),
"operator()()"));
REQUIRE_THAT(src,
HasCall(_A("tmain()::(lambda t20044.cc:84:18)"), _A("A"), "a5()"));
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("A"), "a1()"));
REQUIRE_THAT(src,
HasCall(_A("tmain()"), _A("detail::expected<int,error>"),
"and_then((lambda at t20044.cc:90:19) &&)"));
REQUIRE_THAT(src,
HasCall(_A("detail::expected<int,error>"),
_A("tmain()::(lambda t20044.cc:90:19)"), "operator()()"));
REQUIRE_THAT(src,
HasCall(
_A("tmain()::(lambda t20044.cc:90:19)"), _A("A"), "a2(int)"));
REQUIRE_THAT(src,
HasCall(
_A("A"), _A("detail::expected<int,error>"), "expected(int)"));
save_puml(config.output_directory(), diagram->name + ".puml", src);
}
{
auto j = generate_sequence_json(diagram, *model);
using namespace json;
save_json(config.output_directory(), diagram->name + ".json", j);
}
{
auto src = generate_sequence_mermaid(diagram, *model);
mermaid::AliasMatcher _A(src);
using mermaid::IsClass;
save_mermaid(config.output_directory(), diagram->name + ".mmd", src);
}
}

View File

@@ -470,6 +470,7 @@ using namespace clanguml::test::matchers;
#include "t20041/test_case.h"
#include "t20042/test_case.h"
#include "t20043/test_case.h"
#include "t20044/test_case.h"
///
/// Package diagram tests