diff --git a/src/common/model/diagram_filter.cc b/src/common/model/diagram_filter.cc index a1232308..2f87a20d 100644 --- a/src/common/model/diagram_filter.cc +++ b/src/common/model/diagram_filter.cc @@ -329,13 +329,13 @@ void diagram_filter::init_filters(const config::diagram &c) { // Process inclusive filters if (c.include) { - inclusive_.emplace_back(std::make_unique( + add_inclusive_filter(std::make_unique( filter_t::kInclusive, c.include().namespaces)); - inclusive_.emplace_back(std::make_unique( + add_inclusive_filter(std::make_unique( filter_t::kInclusive, c.include().relationships)); - inclusive_.emplace_back(std::make_unique( + add_inclusive_filter(std::make_unique( filter_t::kInclusive, c.include().access)); - inclusive_.emplace_back(std::make_unique( + add_inclusive_filter(std::make_unique( filter_t::kInclusive, c.base_directory(), c.include().paths)); // Include any of these matches even if one them does not match @@ -347,26 +347,26 @@ void diagram_filter::init_filters(const config::diagram &c) element_filters.emplace_back(std::make_unique( filter_t::kInclusive, c.include().context)); - inclusive_.emplace_back(std::make_unique( + add_inclusive_filter(std::make_unique( filter_t::kInclusive, std::move(element_filters))); } // Process exclusive filters if (c.exclude) { - exclusive_.emplace_back(std::make_unique( + add_exclusive_filter(std::make_unique( filter_t::kExclusive, c.exclude().namespaces)); - exclusive_.emplace_back(std::make_unique( + add_exclusive_filter(std::make_unique( + filter_t::kExclusive, c.base_directory(), c.exclude().paths)); + add_exclusive_filter(std::make_unique( filter_t::kExclusive, c.exclude().elements)); - exclusive_.emplace_back(std::make_unique( + add_exclusive_filter(std::make_unique( filter_t::kExclusive, c.exclude().relationships)); - exclusive_.emplace_back(std::make_unique( + add_exclusive_filter(std::make_unique( filter_t::kExclusive, c.exclude().access)); - exclusive_.emplace_back(std::make_unique( + add_exclusive_filter(std::make_unique( filter_t::kExclusive, c.exclude().subclasses)); - exclusive_.emplace_back(std::make_unique( + add_exclusive_filter(std::make_unique( filter_t::kExclusive, c.exclude().context)); - exclusive_.emplace_back(std::make_unique( - filter_t::kInclusive, c.base_directory(), c.exclude().paths)); } } diff --git a/src/common/model/source_file.h b/src/common/model/source_file.h index a3613938..05c4ab5c 100644 --- a/src/common/model/source_file.h +++ b/src/common/model/source_file.h @@ -90,10 +90,12 @@ public: { std::filesystem::path res; - for (const auto &pe : path_) { - res /= pe; + for (const auto &path_element : path_) { + res /= path_element; } + res /= name(); + if (is_absolute_) res = "/" / res; else diff --git a/src/util/util.cc b/src/util/util.cc index 6eddd2e1..25a90c0e 100644 --- a/src/util/util.cc +++ b/src/util/util.cc @@ -202,32 +202,22 @@ template <> bool starts_with( const std::filesystem::path &path, const std::filesystem::path &prefix) { - if (path == prefix) - return true; + auto normal_path = std::filesystem::path(); + auto normal_prefix = std::filesystem::path(); - const int path_length = std::distance(std::begin(path), std::end(path)); + for (const auto &element : path.lexically_normal()) { + if (!element.empty()) + normal_path /= element; + } - auto last_nonempty_prefix_element = std::prev(std::find_if( - prefix.begin(), prefix.end(), [](auto &&n) { return n.empty(); })); + for (const auto &element : prefix.lexically_normal()) { + if (!element.empty()) + normal_prefix /= element; + } - int prefix_length = - std::distance(std::begin(prefix), last_nonempty_prefix_element); - - // Empty prefix always matches - if (prefix_length == 0) - return true; - - // Prefix longer then path never matches - if (prefix_length >= path_length) - return false; - - auto path_compare_end = path.begin(); - std::advance(path_compare_end, prefix_length); - - std::vector pref(prefix.begin(), last_nonempty_prefix_element); - std::vector pat(path.begin(), path_compare_end); - - return pref == pat; + return std::search(normal_path.begin(), normal_path.end(), + normal_prefix.begin(), + normal_prefix.end()) == normal_path.begin(); } template <> bool starts_with(const std::string &s, const std::string &prefix) diff --git a/src/util/util.h b/src/util/util.h index 5cd75bd9..ed59bf8e 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -144,20 +144,20 @@ template void append(std::vector &l, const std::vector &r) } /** - * @brief Checks if container starts with a prefix. + * @brief Checks if collection starts with a prefix. * * @tparam T e.g. std::vector - * @param con Container to be checked against prefix + * @param col Collection to be checked against prefix * @param prefix Container, which specifies the prefix - * @return true if first prefix.size() elements of con are equal to prefix + * @return true if first prefix.size() elements of col are equal to prefix */ -template bool starts_with(const T &con, const T &prefix) +template bool starts_with(const T &col, const T &prefix) { - if (prefix.size() > con.size()) + if (prefix.size() > col.size()) return false; - return T(prefix.begin(), prefix.end()) == - T(con.begin(), con.begin() + prefix.size()); + return std::search(col.begin(), col.end(), prefix.begin(), prefix.end()) == + col.begin(); } template <> diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6dcf8e55..f3113c7c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -48,6 +48,14 @@ set(CLANG_UML_TEST_CONFIG_HEADER catch.h ) +set(CLANG_UML_TEST_FILTERS_SRC + test_filters.cc + ${TEST_FILTERS_SOURCES} + ) +set(CLANG_UML_TEST_FILTERS_HEADER + catch.h + ) + set(CLANG_UML_TEST_THREAD_POOL_EXECUTOR_SRC test_thread_pool_executor.cc ) @@ -95,6 +103,16 @@ target_link_libraries(test_config ${YAML_CPP_LIBRARIES} spdlog::spdlog clang-umllib cppast) +add_executable(test_filters + ${CLANG_UML_TEST_FILTERS_SRC} + ${CLANG_UML_TEST_FILTERS_HEADER}) + +target_link_libraries(test_filters + PRIVATE + ${LIBCLANG_LIBRARIES} + ${YAML_CPP_LIBRARIES} + spdlog::spdlog clang-umllib cppast) + add_executable(test_thread_pool_executor ${CLANG_UML_TEST_THREAD_POOL_EXECUTOR_SRC} ${CLANG_UML_TEST_THREAD_POOL_EXECUTOR_HEADER}) @@ -145,3 +163,4 @@ add_test(NAME test_config COMMAND test_config) add_test(NAME test_model COMMAND test_model) add_test(NAME test_thread_pool_executor COMMAND test_thread_pool_executor) add_test(NAME test_cases COMMAND test_cases) +add_test(NAME test_filters COMMAND test_filters) diff --git a/tests/t40002/.clang-uml b/tests/t40002/.clang-uml index accff299..69813945 100644 --- a/tests/t40002/.clang-uml +++ b/tests/t40002/.clang-uml @@ -14,6 +14,10 @@ diagrams: # Include only files belonging to these paths paths: - ../../tests/t40002 + exclude: + paths: + # Exclude single header + - ../../tests/t40002/include/lib2/lib2_detail.h plantuml: before: - "' t40002 test include diagram" \ No newline at end of file diff --git a/tests/t40002/include/lib2/lib2.h b/tests/t40002/include/lib2/lib2.h index 3b9fcad5..2f90af6f 100644 --- a/tests/t40002/include/lib2/lib2.h +++ b/tests/t40002/include/lib2/lib2.h @@ -1,5 +1,7 @@ #pragma once +#include "lib2_detail.h" + namespace clanguml::t40002::lib2 { int foo2(); diff --git a/tests/t40002/include/lib2/lib2_detail.h b/tests/t40002/include/lib2/lib2_detail.h new file mode 100644 index 00000000..167a4968 --- /dev/null +++ b/tests/t40002/include/lib2/lib2_detail.h @@ -0,0 +1,7 @@ +#pragma once + +namespace clanguml::t40002::lib2::detail { + +int foo22(); + +} \ No newline at end of file diff --git a/tests/t40002/src/lib2/lib2.cc b/tests/t40002/src/lib2/lib2.cc index a4d06231..89450d88 100644 --- a/tests/t40002/src/lib2/lib2.cc +++ b/tests/t40002/src/lib2/lib2.cc @@ -1,4 +1,5 @@ #include "../../include/lib2/lib2.h" +#include "../../include/lib2/lib2_detail.h" namespace clanguml::t40002::lib2 { @@ -8,4 +9,6 @@ int foo1() { return 1; } int foo() { return foo1(); } +int foo22() { return 22; } + } \ No newline at end of file diff --git a/tests/t40002/test_case.h b/tests/t40002/test_case.h index 3f793053..f261d755 100644 --- a/tests/t40002/test_case.h +++ b/tests/t40002/test_case.h @@ -39,6 +39,7 @@ TEST_CASE("t40002", "[test-case][package]") REQUIRE_THAT(puml, IsFolder("lib2")); REQUIRE_THAT(puml, IsFile("lib1.h")); REQUIRE_THAT(puml, IsFile("lib2.h")); + REQUIRE_THAT(puml, !IsFile("lib2_detail.h")); REQUIRE_THAT(puml, IsFile("t40002.cc")); REQUIRE_THAT(puml, IsFile("lib1.cc")); REQUIRE_THAT(puml, IsFile("lib2.cc")); diff --git a/tests/test_config_data/filters.yml b/tests/test_config_data/filters.yml new file mode 100644 index 00000000..f6bca1e2 --- /dev/null +++ b/tests/test_config_data/filters.yml @@ -0,0 +1,20 @@ +compilation_database_dir: debug +output_directory: output + +diagrams: + include_test: + type: class + glob: + - src/**/*.cc + - src/**/*.h + include: + paths: + - dir1 + - dir2/dir1 + - file1.h + exclude: + paths: + - dir1/file9.h + - dir2/dir1/file9.h + - dir1/dir3 + - file9.h \ No newline at end of file diff --git a/tests/test_filters.cc b/tests/test_filters.cc new file mode 100644 index 00000000..99942631 --- /dev/null +++ b/tests/test_filters.cc @@ -0,0 +1,54 @@ +/** + * tests/test_filters.cc + * + * 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. + */ +#define CATCH_CONFIG_MAIN + +#include "catch.h" + +#include "common/model/diagram_filter.h" +#include "common/model/source_file.h" +#include "config/config.h" + +#include "include_diagram/model/diagram.h" + +#include + +TEST_CASE("Test diagram paths filter", "[unit-test]") +{ + using clanguml::common::model::diagram_filter; + using clanguml::common::model::source_file; + + auto make_path = [](std::string_view p) { + return source_file{ + std::filesystem::current_path() / "test_config_data" / p}; + }; + + auto cfg = clanguml::config::load("./test_config_data/filters.yml"); + + CHECK(cfg.diagrams.size() == 1); + auto &config = *cfg.diagrams["include_test"]; + clanguml::include_diagram::model::diagram diagram; + + diagram_filter filter(diagram, config); + + CHECK(filter.should_include(make_path("dir1/file1.h"))); + CHECK(filter.should_include(make_path("dir1/dir2/file1.h"))); + CHECK(filter.should_include(make_path("dir1/dir2/dir3/dir4/file1.h"))); + CHECK_FALSE(filter.should_include(make_path("dir1/file9.h"))); + CHECK_FALSE(filter.should_include(make_path("dir1/dir3/file1.h"))); + CHECK_FALSE(filter.should_include(make_path("dir2/dir1/file9.h"))); +} diff --git a/tests/test_util.cc b/tests/test_util.cc index d89ea994..0901000a 100644 --- a/tests/test_util.cc +++ b/tests/test_util.cc @@ -55,15 +55,16 @@ TEST_CASE("Test starts_with", "[unit-test]") using std::filesystem::path; CHECK(starts_with(path{"/a/b/c/d"}, path{"/"})); - CHECK(!starts_with(path{"/a/b/c/d"}, path{"/a/b/c/d/e"})); + CHECK_FALSE(starts_with(path{"/a/b/c/d"}, path{"/a/b/c/d/e"})); CHECK(starts_with(path{"/a/b/c/d/e"}, path{"/a/b/c/d"})); CHECK(starts_with(path{"/a/b/c/d/e"}, path{"/a/b/c/d/"})); - CHECK(!starts_with(path{"/e/f/c/d/file.h"}, path{"/a/b"})); - CHECK(!starts_with(path{"/e/f/c/d/file.h"}, path{"/a/b/"})); + CHECK_FALSE(starts_with(path{"/e/f/c/d/file.h"}, path{"/a/b"})); + CHECK_FALSE(starts_with(path{"/e/f/c/d/file.h"}, path{"/a/b/"})); CHECK(starts_with(path{"/a/b/c/d/file.h"}, path{"/a/b/c"})); CHECK(starts_with(path{"/a/b/c/file.h"}, path{"/a/b/c/file.h"})); CHECK(starts_with(path{"c/file.h"}, path{"c"})); CHECK(starts_with(path{"c/file.h"}, path{"c/"})); + CHECK_FALSE(starts_with(path{"c/file1.h"}, path{"c/file2.h"})); } TEST_CASE("Test replace_all", "[unit-test]")