From efc34bcec6a15b5f235cb0bc3c4838792b4a3504 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 28 Apr 2024 00:57:46 +0200 Subject: [PATCH] Fixed handling of nested lambda expressions in sequence diagrams --- .../visitor/call_expression_context.cc | 3 + .../visitor/translation_unit_visitor.cc | 122 ++++++++++++++---- .../visitor/translation_unit_visitor.h | 3 + tests/t20045/.clang-uml | 13 ++ tests/t20045/t20045.cc | 47 +++++++ tests/t20045/test_case.h | 82 ++++++++++++ tests/t20046/.clang-uml | 13 ++ tests/t20046/t20046.cc | 24 ++++ tests/t20046/test_case.h | 94 ++++++++++++++ tests/test_cases.cc | 2 + tests/test_cases.h | 2 +- tests/test_cases.yaml | 9 ++ util/generate_test_case.py | 43 +++--- 13 files changed, 407 insertions(+), 50 deletions(-) create mode 100644 tests/t20045/.clang-uml create mode 100644 tests/t20045/t20045.cc create mode 100644 tests/t20045/test_case.h create mode 100644 tests/t20046/.clang-uml create mode 100644 tests/t20046/t20046.cc create mode 100644 tests/t20046/test_case.h diff --git a/src/sequence_diagram/visitor/call_expression_context.cc b/src/sequence_diagram/visitor/call_expression_context.cc index 403d460f..8d49c065 100644 --- a/src/sequence_diagram/visitor/call_expression_context.cc +++ b/src/sequence_diagram/visitor/call_expression_context.cc @@ -132,6 +132,9 @@ void call_expression_context::update( std::int64_t call_expression_context::caller_id() const { + if (lambda_caller_id() != 0) + return lambda_caller_id(); + return current_caller_id_; } diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index e6ee6651..ce866539 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -455,8 +455,10 @@ bool translation_unit_visitor::VisitLambdaExpr(clang::LambdaExpr *expr) lambda_method_model_ptr->set_method_name(method_name); lambda_method_model_ptr->set_class_id(cls_id); - lambda_method_model_ptr->set_class_full_name( - lambda_class_model_ptr->full_name(false)); + + // If this is a nested lambda, prepend the parent lambda name to this lambda + auto lambda_class_full_name = lambda_class_model_ptr->full_name(false); + lambda_method_model_ptr->set_class_full_name(lambda_class_full_name); diagram().add_participant(std::move(lambda_class_model_ptr)); @@ -469,9 +471,8 @@ bool translation_unit_visitor::VisitLambdaExpr(clang::LambdaExpr *expr) // If lambda expression is in an argument to a method/function, and that // method function would be excluded by filters if (std::holds_alternative( - context().current_callexpr())/* && - !should_include( - std::get(context().current_callexpr()))*/) { + context().current_callexpr()) && + (context().lambda_caller_id() == 0)) { using clanguml::common::model::message_t; using clanguml::sequence_diagram::model::message; @@ -504,9 +505,6 @@ bool translation_unit_visitor::VisitLambdaExpr(clang::LambdaExpr *expr) bool translation_unit_visitor::TraverseLambdaExpr(clang::LambdaExpr *expr) { - const auto lambda_full_name = - expr->getLambdaClass()->getCanonicalDecl()->getNameAsString(); - RecursiveASTVisitor::TraverseLambdaExpr(expr); // lambda context is entered inside the visitor @@ -543,10 +541,8 @@ bool translation_unit_visitor::TraverseCXXMemberCallExpr( if (source_manager().isInSystemHeader(expr->getSourceRange().getBegin())) return true; - LOG_DBG("Entering member call expression at {} to {}::{}", - expr->getBeginLoc().printToString(source_manager()), - common::to_string(expr->getObjectType(), context().get_ast_context()), - common::to_string(expr->getMethodDecl())); + LOG_DBG("Entering member call expression at {}", + expr->getBeginLoc().printToString(source_manager())); context().enter_callexpr(expr); @@ -599,6 +595,9 @@ bool translation_unit_visitor::TraverseCXXTemporaryObjectExpr( bool translation_unit_visitor::TraverseCXXConstructExpr( clang::CXXConstructExpr *expr) { + LOG_DBG("Entering cxx construct call expression at {}", + expr->getBeginLoc().printToString(source_manager())); + context().enter_callexpr(expr); RecursiveASTVisitor::TraverseCXXConstructExpr( @@ -606,6 +605,9 @@ bool translation_unit_visitor::TraverseCXXConstructExpr( translation_unit_visitor::VisitCXXConstructExpr(expr); + LOG_DBG("Leaving member call expression at {}", + expr->getBeginLoc().printToString(source_manager())); + context().leave_callexpr(); pop_message_to_diagram(expr); @@ -1057,13 +1059,6 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) if (!generated_message_from_comment && !should_include(expr)) return true; - // If we're currently inside a lambda expression, set it's id as - // message source rather then enclosing context - // Unless the lambda is declared in a function or method call - if (context().lambda_caller_id() != 0) { - m.set_from(context().lambda_caller_id()); - } - if (context().is_expr_in_current_control_statement_condition(expr)) { m.set_message_scope(common::model::message_scope_t::kCondition); } @@ -1100,9 +1095,12 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) if (callee_decl == nullptr) { LOG_DBG("Cannot get callee declaration - trying direct function " "callee..."); + callee_decl = expr->getDirectCallee(); - LOG_DBG( - "Found function/method callee in: {}", common::to_string(expr)); + + if (callee_decl != nullptr) + LOG_DBG("Found function/method callee in: {}", + common::to_string(expr)); } if (callee_decl == nullptr) { @@ -1124,6 +1122,17 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) if (!process_unresolved_lookup_call_expression(m, expr)) return true; } + else if (clang::dyn_cast_or_null( + expr->getCallee()) != nullptr) { + LOG_DBG("Processing lambda expression callee"); + if (!process_lambda_call_expression(m, expr)) + return true; + } + else { + LOG_DBG("Found unsupported callee decl type for: {} at {}", + common::to_string(expr), + expr->getBeginLoc().printToString(source_manager())); + } } else { auto success = process_function_call_expression(m, expr); @@ -1219,10 +1228,6 @@ bool translation_unit_visitor::VisitCXXConstructExpr( set_source_location(*expr, m); - if (context().lambda_caller_id() != 0) { - m.set_from(context().lambda_caller_id()); - } - if (context().is_expr_in_current_control_statement_condition(expr)) { m.set_message_scope(common::model::message_scope_t::kCondition); } @@ -1487,6 +1492,26 @@ bool translation_unit_visitor::process_function_call_expression( return true; } +bool translation_unit_visitor::process_lambda_call_expression( + model::message &m, const clang::CallExpr *expr) const +{ + const auto *lambda_expr = + clang::dyn_cast_or_null(expr->getCallee()); + + if (lambda_expr == nullptr) + return true; + + const auto lambda_class_id = lambda_expr->getLambdaClass()->getID(); + const auto maybe_id = get_unique_id(lambda_class_id); + if (!maybe_id.has_value()) + m.set_to(lambda_class_id); + else { + m.set_to(maybe_id.value()); + } + + return true; +} + bool translation_unit_visitor::process_unresolved_lookup_call_expression( model::message &m, const clang::CallExpr *expr) const { @@ -1498,7 +1523,6 @@ bool translation_unit_visitor::process_unresolved_lookup_call_expression( for (const auto *decl : unresolved_expr->decls()) { if (clang::dyn_cast_or_null(decl) != nullptr) { - // Yes, it's a template const auto *ftd = clang::dyn_cast_or_null(decl); @@ -1511,6 +1535,23 @@ bool translation_unit_visitor::process_unresolved_lookup_call_expression( break; } + else if (clang::dyn_cast_or_null(decl) != + nullptr) { + const auto *fd = + clang::dyn_cast_or_null(decl); + + const auto maybe_id = get_unique_id(fd->getID()); + if (!maybe_id.has_value()) + m.set_to(fd->getID()); + else { + m.set_to(maybe_id.value()); + } + + break; + } + else { + LOG_DBG("Unknown unresolved lookup expression"); + } } } @@ -1568,9 +1609,10 @@ translation_unit_visitor::create_class_model(clang::CXXRecordDecl *cls) const auto *parent = cls->getParent(); if ((parent != nullptr) && parent->isRecord()) { - // Here we have 2 options, either: + // Here we have 3 options, either: // - the parent is a regular C++ class/struct // - the parent is a class template declaration/specialization + // - the parent is a lambda (i.e. this is a nested lambda expression) std::optional id_opt; const auto *parent_record_decl = clang::dyn_cast(parent); @@ -1817,7 +1859,31 @@ std::string translation_unit_visitor::make_lambda_name( const auto location = cls->getLocation(); const std::string source_location{lambda_source_location(location)}; - if (context().caller_id() != 0 && + if (context().lambda_caller_id() != 0) { + // Parent is also a lambda (this id points to a lambda operator()) + std::string parent_lambda_class_name{"()"}; + if (diagram().get_participant( + context().lambda_caller_id())) { + auto parent_lambda_class_id = diagram() + .get_participant( + context().lambda_caller_id()) + .value() + .class_id(); + + if (diagram().get_participant( + parent_lambda_class_id)) { + parent_lambda_class_name = + diagram() + .get_participant(parent_lambda_class_id) + .value() + .full_name(false); + } + } + + result = fmt::format( + "{}##(lambda {})", parent_lambda_class_name, source_location); + } + else if (context().caller_id() != 0 && get_participant(context().caller_id()).has_value()) { auto parent_full_name = get_participant(context().caller_id()).value().full_name_no_ns(); diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.h b/src/sequence_diagram/visitor/translation_unit_visitor.h index 2c3cebb4..e76f90d7 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.h +++ b/src/sequence_diagram/visitor/translation_unit_visitor.h @@ -436,6 +436,9 @@ private: bool process_unresolved_lookup_call_expression( model::message &m, const clang::CallExpr *expr) const; + bool process_lambda_call_expression( + model::message &m, const clang::CallExpr *expr) const; + /** * @brief Register a message model `m` with a call expression * diff --git a/tests/t20045/.clang-uml b/tests/t20045/.clang-uml new file mode 100644 index 00000000..77290090 --- /dev/null +++ b/tests/t20045/.clang-uml @@ -0,0 +1,13 @@ +add_compile_flags: + - -fparse-all-comments +diagrams: + t20045_sequence: + type: sequence + glob: + - t20045.cc + include: + namespaces: + - clanguml::t20045 + using_namespace: clanguml::t20045 + from: + - function: "clanguml::t20045::tmain()" \ No newline at end of file diff --git a/tests/t20045/t20045.cc b/tests/t20045/t20045.cc new file mode 100644 index 00000000..d7cd3e1f --- /dev/null +++ b/tests/t20045/t20045.cc @@ -0,0 +1,47 @@ +namespace clanguml { +namespace t20045 { + +template int a1(F &&f) { return f(42); } + +int a2(int x) { return 2; } + +int a3(int x) { return 3; } + +struct B { + int b1(int x) { return x + 1; } + int b2(int x) { return x + 2; } +}; + +class C { +public: + explicit C(int x) + : x_{x} + { + } + + int get_x() const { return x_; } + +private: + int x_; +}; + +int tmain() +{ + B b; + + // \uml{call clanguml::t20045::a2(int)} + auto v1 = a1(a2); + + auto v2 = a1([](auto &&arg) { return a3(arg); }); + + auto v3 = a1([&](auto &&arg) { return b.b1(arg); }); + + auto v4 = a1([](auto &&arg) { + C c(arg); + return c.get_x(); + }); + + return 0; +} +} +} \ No newline at end of file diff --git a/tests/t20045/test_case.h b/tests/t20045/test_case.h new file mode 100644 index 00000000..a3c56b10 --- /dev/null +++ b/tests/t20045/test_case.h @@ -0,0 +1,82 @@ +/** + * tests/t20045/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("t20045", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20045"); + + auto diagram = config.diagrams["t20045_sequence"]; + + REQUIRE(diagram->name == "t20045_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20045_sequence"); + + { + auto src = generate_sequence_puml(diagram, *model); + AliasMatcher _A(src); + + REQUIRE_THAT(src, StartsWith("@startuml")); + REQUIRE_THAT(src, EndsWith("@enduml\n")); + + REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("a2(int)"), "")); + + REQUIRE_THAT(src, + HasCall(_A("tmain()"), + _A("a1<(lambda at t20045.cc:35:18)>((lambda at " + "t20045.cc:35:18) &&)"), + "")); + + REQUIRE_THAT(src, + HasCall(_A("a1<(lambda at t20045.cc:35:18)>((lambda at " + "t20045.cc:35:18) &&)"), + _A("tmain()::(lambda t20045.cc:35:18)"), "operator()()")); + + REQUIRE_THAT(src, + HasCall( + _A("tmain()::(lambda t20045.cc:35:18)"), _A("a3(int)"), "")); + + REQUIRE_THAT(src, + HasCall( + _A("tmain()::(lambda t20045.cc:37:18)"), _A("B"), "b1(int)")); + + REQUIRE_THAT(src, + HasCall( + _A("tmain()::(lambda t20045.cc:39:18)"), _A("C"), "get_x()")); + + 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); + } +} \ No newline at end of file diff --git a/tests/t20046/.clang-uml b/tests/t20046/.clang-uml new file mode 100644 index 00000000..2db8bf1c --- /dev/null +++ b/tests/t20046/.clang-uml @@ -0,0 +1,13 @@ +add_compile_flags: + - -fparse-all-comments +diagrams: + t20046_sequence: + type: sequence + glob: + - t20046.cc + include: + namespaces: + - clanguml::t20046 + using_namespace: clanguml::t20046 + from: + - function: "clanguml::t20046::tmain()" \ No newline at end of file diff --git a/tests/t20046/t20046.cc b/tests/t20046/t20046.cc new file mode 100644 index 00000000..16ad93dd --- /dev/null +++ b/tests/t20046/t20046.cc @@ -0,0 +1,24 @@ +namespace clanguml { +namespace t20046 { + +template int a1(F &&f) { return f(42); } + +int a2(int x) { return 2; } + +int a3(int x) { return 3; } + +int tmain() +{ + // Call expression in a nested lambda + auto v1 = [](auto &&arg1) { + return [](auto &&arg2) { return a2(arg2); }(arg1); + }(0); + + // Call expression in a nested lambda in call expression + auto v4 = a1( + [](auto &&arg1) { return [](auto &&arg2) { return a3(arg2); }(arg1); }); + + return 0; +} +} +} \ No newline at end of file diff --git a/tests/t20046/test_case.h b/tests/t20046/test_case.h new file mode 100644 index 00000000..ff71025c --- /dev/null +++ b/tests/t20046/test_case.h @@ -0,0 +1,94 @@ +/** + * tests/t20046/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("t20046", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20046"); + + auto diagram = config.diagrams["t20046_sequence"]; + + REQUIRE(diagram->name == "t20046_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20046_sequence"); + + { + auto src = generate_sequence_puml(diagram, *model); + AliasMatcher _A(src); + + REQUIRE_THAT(src, StartsWith("@startuml")); + REQUIRE_THAT(src, EndsWith("@enduml\n")); + + REQUIRE_THAT(src, + HasCall(_A("tmain()"), _A("tmain()::(lambda t20046.cc:13:15)"), + "operator()()")); + REQUIRE_THAT(src, + HasCall(_A("tmain()::(lambda t20046.cc:13:15)"), + _A("tmain()::(lambda t20046.cc:13:15)::(lambda " + "t20046.cc:14:16)"), + "operator()()")); + + REQUIRE_THAT(src, + HasCall(_A("tmain()::(lambda t20046.cc:13:15)::(lambda " + "t20046.cc:14:16)"), + _A("a2(int)"), "")); + + REQUIRE_THAT(src, + HasCall(_A("tmain()"), + _A("a1<(lambda at t20046.cc:19:9)>((lambda at t20046.cc:19:9) " + "&&)"), + "")); + + REQUIRE_THAT(src, + HasCall( + _A("a1<(lambda at t20046.cc:19:9)>((lambda at t20046.cc:19:9) " + "&&)"), + _A("tmain()::(lambda t20046.cc:19:9)"), "operator()()")); + + REQUIRE_THAT(src, + HasCall(_A("tmain()::(lambda t20046.cc:19:9)"), + _A("tmain()::(lambda t20046.cc:19:9)::(lambda " + "t20046.cc:19:34)"), + "operator()()")); + + REQUIRE_THAT(src, + HasCall(_A("tmain()::(lambda t20046.cc:19:9)::(lambda " + "t20046.cc:19:34)"), + _A("a3(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); + } +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 8dc2598b..3253c6ae 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -471,6 +471,8 @@ using namespace clanguml::test::matchers; #include "t20042/test_case.h" #include "t20043/test_case.h" #include "t20044/test_case.h" +#include "t20045/test_case.h" +#include "t20046/test_case.h" /// /// Package diagram tests diff --git a/tests/test_cases.h b/tests/test_cases.h index 4b16e59c..6cc3cb06 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -400,7 +400,7 @@ struct AliasMatcher { } } - return "__INVALID__ALIAS__"; + return fmt::format("__INVALID__ALIAS__({})", name); } const std::vector puml; diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 1f611954..a2675040 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -352,6 +352,15 @@ test_cases: - name: t20043 title: Test case for elements diagram filter in sequence diagrams description: + - name: t20044 + title: Test case for template method call expressions with callables + description: + - name: t20045 + title: Test case for template function call expressions with callables + description: + - name: t20046 + title: Test case for call expressions in nested lambdas + description: Package diagrams: - name: t30001 title: Basic package diagram test case diff --git a/util/generate_test_case.py b/util/generate_test_case.py index e024c180..a2a95163 100755 --- a/util/generate_test_case.py +++ b/util/generate_test_case.py @@ -26,63 +26,64 @@ TEST_CASE_MULTIPLIER = 10000 CLASS_DIAGRAM_TEST_CASE_EXAMPLES = """ // Check if all classes exist - //REQUIRE_THAT(puml, IsClass(_A("A"))); + //REQUIRE_THAT(src, IsClass(_A("A"))); // Check if class templates exist - //REQUIRE_THAT(puml, IsClassTemplate("A", "T,P,CMP,int N")); + //REQUIRE_THAT(src, IsClassTemplate("A", "T,P,CMP,int N")); // Check concepts - //REQUIRE_THAT(puml, IsConcept(_A("AConcept"))); - //REQUIRE_THAT(puml, + //REQUIRE_THAT(src, IsConcept(_A("AConcept"))); + //REQUIRE_THAT(src, // IsConceptRequirement( // _A("AConcept"), "sizeof (T) > sizeof (P)")); // Check if all enums exist - //REQUIRE_THAT(puml, IsEnum(_A("Lights"))); + //REQUIRE_THAT(src, IsEnum(_A("Lights"))); // Check if all inner classes exist - //REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("AA"))); + //REQUIRE_THAT(src, IsInnerClass(_A("A"), _A("AA"))); // Check if all inheritance relationships exist - //REQUIRE_THAT(puml, IsBaseClass(_A("Base"), _A("Child"))); + //REQUIRE_THAT(src, IsBaseClass(_A("Base"), _A("Child"))); // Check if all methods exist - //REQUIRE_THAT(puml, (IsMethod("foo"))); + //REQUIRE_THAT(src, (IsMethod("foo"))); // Check if all fields exist - //REQUIRE_THAT(puml, (IsField("private_member", "int"))); + //REQUIRE_THAT(src, (IsField("private_member", "int"))); // Check if all relationships exist - //REQUIRE_THAT(puml, IsAssociation(_A("D"), _A("A"), "-as")); - //REQUIRE_THAT(puml, IsDependency(_A("R"), _A("B"))); - //REQUIRE_THAT(puml, IsAggregation(_A("R"), _A("D"), "-ag")); - //REQUIRE_THAT(puml, IsComposition(_A("R"), _A("D"), "-ac")); - //REQUIRE_THAT(puml, IsInstantiation(_A("ABCD::F"), _A("F"))); + //REQUIRE_THAT(src, IsAssociation(_A("D"), _A("A"), "-as")); + //REQUIRE_THAT(src, IsDependency(_A("R"), _A("B"))); + //REQUIRE_THAT(src, IsAggregation(_A("R"), _A("D"), "-ag")); + //REQUIRE_THAT(src, IsComposition(_A("R"), _A("D"), "-ac")); + //REQUIRE_THAT(src, IsInstantiation(_A("ABCD::F"), _A("F"))); """ SEQUENCE_DIAGRAM_TEST_CASE_EXAMPLES = """ // Check if all calls exist - //REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a()")); - //REQUIRE_THAT(puml, HasCall(_A("A"), "a()")); + //REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("A"), "a()")); + //REQUIRE_THAT(src, HasCall(_A("A"), "a()")); """ PACKAGE_DIAGRAM_TEST_CASE_EXAMPLES = """ // Check if all packages exist - //REQUIRE_THAT(puml, IsPackage("ns1")); + //REQUIRE_THAT(src, IsPackage("ns1")); """ INCLUDE_DIAGRAM_TEST_CASE_EXAMPLES = """ // Check all folders exist - //REQUIRE_THAT(puml, IsFolder("lib1")); + //REQUIRE_THAT(src, IsFolder("lib1")); // Check if all files exist - //REQUIRE_THAT(puml, IsFile("lib1.h")); + //REQUIRE_THAT(src, IsFile("lib1.h")); // Check if all includes exists - //REQUIRE_THAT(puml, IsAssociation(_A("t40002.cc"), _A("lib1.h"))); - //REQUIRE_THAT(puml, IsDependency(_A("t40001_include1.h"), _A("string"))); + //REQUIRE_THAT(src, IsAssociation(_A("t40002.cc"), _A("lib1.h"))); + //REQUIRE_THAT(src, IsDependency(_A("t40001_include1.h"), _A("string"))); """ + def test_case_already_exists(name): return os.path.isdir(os.path.join(os.path.dirname(__file__), '..', name))