diff --git a/plugins/cpp/model/include/model/cppfunction.h b/plugins/cpp/model/include/model/cppfunction.h index b6e39395f..81eadfd52 100644 --- a/plugins/cpp/model/include/model/cppfunction.h +++ b/plugins/cpp/model/include/model/cppfunction.h @@ -36,7 +36,7 @@ struct CppFunction : CppTypedEntity typedef std::shared_ptr CppFunctionPtr; #pragma db view \ - object(CppFunction) object(CppVariable = Parameters : CppFunction::parameters) + object(CppFunction) object(CppVariable = Parameters inner : CppFunction::parameters) struct CppFunctionParamCount { #pragma db column("count(" + Parameters::id + ")") @@ -45,7 +45,7 @@ struct CppFunctionParamCount #pragma db view \ object(CppFunction) \ - object(CppVariable = Parameters : CppFunction::parameters) \ + object(CppVariable = Parameters inner : CppFunction::parameters) \ object(CppAstNode : CppFunction::astNodeId == CppAstNode::id) \ object(File : CppAstNode::location.file) \ query((?) + "GROUP BY" + cc::model::CppEntity::astNodeId + "," + cc::model::File::path) @@ -59,7 +59,7 @@ struct CppFunctionParamCountWithId }; #pragma db view \ - object(CppFunction) object(CppVariable = Locals : CppFunction::locals) + object(CppFunction) object(CppVariable = Locals inner : CppFunction::locals) struct CppFunctionLocalCount { #pragma db column("count(" + Locals::id + ")") @@ -97,7 +97,7 @@ struct CppFunctionBumpyRoad #pragma db view \ object(CppFunction) \ - object(CppVariable = Parameters : CppFunction::parameters) + object(CppVariable = Parameters inner : CppFunction::parameters) struct CppFunctionParamTypeView { #pragma db column(CppFunction::astNodeId) @@ -109,7 +109,7 @@ struct CppFunctionParamTypeView #pragma db view \ object(CppFunction) \ - object(CppVariable = Locals : CppFunction::locals) + object(CppVariable = Locals inner : CppFunction::locals) struct CppFunctionLocalTypeView { #pragma db column(CppFunction::astNodeId) diff --git a/plugins/cpp_metrics/model/CMakeLists.txt b/plugins/cpp_metrics/model/CMakeLists.txt index 38d8ca343..21a812fc2 100644 --- a/plugins/cpp_metrics/model/CMakeLists.txt +++ b/plugins/cpp_metrics/model/CMakeLists.txt @@ -5,11 +5,12 @@ include_directories( set(ODB_SOURCES include/model/cppastnodemetrics.h include/model/cppcohesionmetrics.h - include/model/cppfilemetrics.h) + include/model/cppfilemetrics.h + include/model/cpptypedependencymetrics.h) generate_odb_files("${ODB_SOURCES}" "cpp") add_odb_library(cppmetricsmodel ${ODB_CXX_SOURCES}) target_link_libraries(cppmetricsmodel cppmodel) -install_sql() \ No newline at end of file +install_sql() diff --git a/plugins/cpp_metrics/model/include/model/cppfilemetrics.h b/plugins/cpp_metrics/model/include/model/cppfilemetrics.h index 248c2a710..b5f6f24dc 100644 --- a/plugins/cpp_metrics/model/include/model/cppfilemetrics.h +++ b/plugins/cpp_metrics/model/include/model/cppfilemetrics.h @@ -13,7 +13,7 @@ struct CppFileMetrics { enum Type { - PLACEHOLDER + EFFERENT_MODULE }; #pragma db id auto diff --git a/plugins/cpp_metrics/model/include/model/cpptypedependencymetrics.h b/plugins/cpp_metrics/model/include/model/cpptypedependencymetrics.h new file mode 100644 index 000000000..cca2370e5 --- /dev/null +++ b/plugins/cpp_metrics/model/include/model/cpptypedependencymetrics.h @@ -0,0 +1,69 @@ +#ifndef CC_MODEL_CPPTYPEDEPENDENCYMETRICS_H +#define CC_MODEL_CPPTYPEDEPENDENCYMETRICS_H + +#include +#include +#include +#include +#include +#include + +namespace cc +{ +namespace model +{ + +#pragma db object +struct CppTypeDependencyMetrics +{ + #pragma db id auto + std::uint64_t id; + + #pragma db not_null + std::uint64_t entityHash; + + #pragma db not_null + std::uint64_t dependencyHash; +}; + +#pragma db view \ + object(CppTypeDependencyMetrics) \ + object(CppAstNode = EntityAstNode : CppTypeDependencyMetrics::entityHash == EntityAstNode::entityHash \ + && EntityAstNode::astType == cc::model::CppAstNode::AstType::Definition) \ + object(File = EntityFile : EntityAstNode::location.file == EntityFile::id) \ + object(CppAstNode = DependencyAstNode : CppTypeDependencyMetrics::dependencyHash == DependencyAstNode::entityHash \ + && DependencyAstNode::astType == cc::model::CppAstNode::AstType::Definition) \ + object(File = DependencyFile : DependencyAstNode::location.file == DependencyFile::id) +struct CppTypeDependencyMetricsPathView +{ + #pragma db column(CppTypeDependencyMetrics::entityHash) + std::size_t entityHash; + + #pragma db column(CppTypeDependencyMetrics::dependencyHash) + std::size_t dependencyHash; + + #pragma db column(EntityFile::path) + std::string entityPath; + + #pragma db column(DependencyFile::path) + std::string dependencyPath; +}; + +#pragma db view \ + object(CppTypeDependencyMetrics) \ + object(CppAstNode = EntityAstNode : CppTypeDependencyMetrics::entityHash == EntityAstNode::entityHash \ + && EntityAstNode::astType == cc::model::CppAstNode::AstType::Definition) \ + object(File = EntityFile : EntityAstNode::location.file == EntityFile::id) \ + object(CppAstNode = DependencyAstNode : CppTypeDependencyMetrics::dependencyHash == DependencyAstNode::entityHash \ + && DependencyAstNode::astType == cc::model::CppAstNode::AstType::Definition) \ + object(File = DependencyFile : DependencyAstNode::location.file == DependencyFile::id) +struct CppTypeDependencyMetricsPathViewDistinctCount +{ + #pragma db column("count(distinct" + CppTypeDependencyMetrics::dependencyHash + ")") + std::size_t count; +}; + +} // model +} // cc + +#endif // CC_MODEL_CPPTYPEDEPENDENCYMETRICS_H diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 357904549..4f9c21f9e 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -80,7 +80,10 @@ class CppMetricsParser : public AbstractParser void efferentTypeLevel(); // Calculate the afferent coupling of types. void afferentTypeLevel(); - + // Calculate the efferent coupling at module level. + void efferentModuleLevel(); + // Returns module path query based on parser configuration. + odb::query getModulePathsQuery(); /// @brief Constructs an ODB query that you can use to filter only /// the database records of the given parameter type whose path @@ -203,6 +206,7 @@ class CppMetricsParser : public AbstractParser static const int lackOfCohesionPartitionMultiplier = 25; static const int efferentCouplingTypesPartitionMultiplier = 5; static const int afferentCouplingTypesPartitionMultiplier = 5; + static const int efferentCouplingModulesPartitionMultiplier = 5; }; } // parser diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 4b71bbee2..55a390147 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -10,9 +10,12 @@ #include #include #include - +#include +#include #include #include +#include +#include #include @@ -390,7 +393,9 @@ void CppMetricsParser::efferentTypeLevel() dependentTypes.clear(); // Count parent types - auto inheritanceView = _ctx.db->query( + auto inheritanceView = _ctx.db->query( + InheritanceQuery::derived == type.entityHash); + auto inheritanceCount = _ctx.db->query_value( InheritanceQuery::derived == type.entityHash); // Count unique attribute types @@ -423,8 +428,26 @@ void CppMetricsParser::efferentTypeLevel() model::CppAstNodeMetrics metric; metric.astNodeId = type.astNodeId; metric.type = model::CppAstNodeMetrics::Type::EFFERENT_TYPE; - metric.value = inheritanceView.begin()->count + dependentTypes.size(); + metric.value = inheritanceCount.count + dependentTypes.size(); _ctx.db->persist(metric); + + auto typeRelationInserter = [this](const std::uint64_t& entityHash, const std::uint64_t& dependencyHash) + { + model::CppTypeDependencyMetrics relation; + relation.entityHash = entityHash; + relation.dependencyHash = dependencyHash; + _ctx.db->persist(relation); + }; + + // Insert type dependency relations + for (const std::uint64_t& d : dependentTypes) { + typeRelationInserter(type.entityHash, d); + } + + // Insert inheritance relations + for (const model::CppInheritance& d : inheritanceView) { + typeRelationInserter(type.entityHash, d.base); + } } }); }); @@ -513,6 +536,46 @@ void CppMetricsParser::afferentTypeLevel() }); } +odb::query CppMetricsParser::getModulePathsQuery() +{ + if (_ctx.moduleDirectories.empty()) { + // No module directories specified, compute for all directories + return odb::query::type == cc::model::File::DIRECTORY_TYPE && getFilterPathsQuery(); + } else { + // Compute for module directories + return odb::query::path.in_range(_ctx.moduleDirectories.begin(), _ctx.moduleDirectories.end()); + } +} + +void CppMetricsParser::efferentModuleLevel() +{ + parallelCalcMetric( + "Efferent coupling at module level", + _threadCount * efferentCouplingModulesPartitionMultiplier,// number of jobs; adjust for granularity + getModulePathsQuery(), + [&, this](const MetricsTasks& tasks) + { + util::OdbTransaction{_ctx.db}([&, this] + { + typedef odb::query TypeDependencyQuery; + typedef model::CppTypeDependencyMetricsPathViewDistinctCount TypeDependencyResult; + + for (const model::File& file : tasks) + { + TypeDependencyResult types = _ctx.db->query_value( + TypeDependencyQuery::EntityFile::path.like(file.path + '%') && + !TypeDependencyQuery::DependencyFile::path.like(file.path + '%')); + + model::CppFileMetrics metric; + metric.file = file.id; + metric.type = model::CppFileMetrics::Type::EFFERENT_MODULE; + metric.value = types.count; + _ctx.db->persist(metric); + } + }); + }); +} + bool CppMetricsParser::parse() { LOG(info) << "[cppmetricsparser] Computing function parameter count metric."; @@ -529,6 +592,8 @@ bool CppMetricsParser::parse() efferentTypeLevel(); LOG(info) << "[cppmetricsparser] Computing afferent coupling metric for types."; afferentTypeLevel(); + LOG(info) << "[cppmetricsparser] Computing efferent coupling metric at module level."; + efferentModuleLevel(); // This metric needs to be calculated after efferentTypeLevel return true; } diff --git a/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt b/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt index 650aa24c0..88ccb5fe4 100644 --- a/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt +++ b/plugins/cpp_metrics/test/sources/parser/CMakeLists.txt @@ -6,4 +6,5 @@ add_library(CppMetricsTestProject STATIC typemccabe.cpp lackofcohesion.cpp bumpyroad.cpp - afferentcoupling.cpp) + afferentcoupling.cpp + modulemetrics.cpp) diff --git a/plugins/cpp_metrics/test/sources/parser/module_a/a1.h b/plugins/cpp_metrics/test/sources/parser/module_a/a1.h new file mode 100644 index 000000000..a5c0195a4 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/module_a/a1.h @@ -0,0 +1,13 @@ +#ifndef CC_CPP_MODULE_METRICS_TEST_A1 +#define CC_CPP_MODULE_METRICS_TEST_A1 + +#include "./a2.h" + +namespace CC_CPP_MODULE_METRICS_TEST +{ +class A1 { + A2 a2; +}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/module_a/a2.h b/plugins/cpp_metrics/test/sources/parser/module_a/a2.h new file mode 100644 index 000000000..dac330711 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/module_a/a2.h @@ -0,0 +1,13 @@ +#ifndef CC_CPP_MODULE_METRICS_TEST_A2 +#define CC_CPP_MODULE_METRICS_TEST_A2 + +#include "../module_b/b1.h" + +namespace CC_CPP_MODULE_METRICS_TEST +{ +class A2 { + B1 b1; +}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/module_b/b1.h b/plugins/cpp_metrics/test/sources/parser/module_b/b1.h new file mode 100644 index 000000000..cc3d57175 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/module_b/b1.h @@ -0,0 +1,13 @@ +#ifndef CC_CPP_MODULE_METRICS_TEST_B1 +#define CC_CPP_MODULE_METRICS_TEST_B1 + +#include "./b2.h" + +namespace CC_CPP_MODULE_METRICS_TEST +{ +class B1 { + B2 b2; +}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/module_b/b2.h b/plugins/cpp_metrics/test/sources/parser/module_b/b2.h new file mode 100644 index 000000000..b76e00b4e --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/module_b/b2.h @@ -0,0 +1,9 @@ +#ifndef CC_CPP_MODULE_METRICS_TEST_B2 +#define CC_CPP_MODULE_METRICS_TEST_B2 + +namespace CC_CPP_MODULE_METRICS_TEST +{ +class B2 {}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/module_c/c1.h b/plugins/cpp_metrics/test/sources/parser/module_c/c1.h new file mode 100644 index 000000000..d8e73bc44 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/module_c/c1.h @@ -0,0 +1,15 @@ +#ifndef CC_CPP_MODULE_METRICS_TEST_C1 +#define CC_CPP_MODULE_METRICS_TEST_C1 + +#include "../module_b/b1.h" +#include "./c2.h" + +namespace CC_CPP_MODULE_METRICS_TEST +{ +class C1 { + B1 b1; + C2 c2; +}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/module_c/c2.h b/plugins/cpp_metrics/test/sources/parser/module_c/c2.h new file mode 100644 index 000000000..b5f00b5ef --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/module_c/c2.h @@ -0,0 +1,15 @@ +#ifndef CC_CPP_MODULE_METRICS_TEST_C2 +#define CC_CPP_MODULE_METRICS_TEST_C2 + +#include "../module_a/a2.h" +#include "../module_b/b1.h" + +namespace CC_CPP_MODULE_METRICS_TEST +{ +class C2 { + A2 a2; + B1 b1; +}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/modulemetrics.cpp b/plugins/cpp_metrics/test/sources/parser/modulemetrics.cpp new file mode 100644 index 000000000..9c6e16c34 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/modulemetrics.cpp @@ -0,0 +1,6 @@ +#include "./module_a/a1.h" +#include "./module_a/a2.h" +#include "./module_b/b1.h" +#include "./module_b/b2.h" +#include "./module_c/c1.h" +#include "./module_c/c2.h" diff --git a/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp b/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp index 677cb4486..3ab40e57b 100644 --- a/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp +++ b/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include @@ -21,6 +23,8 @@ using namespace cc; extern char* dbConnectionString; +typedef std::pair StringUintParam; + class CppMetricsParserTest : public ::testing::Test { public: @@ -62,14 +66,12 @@ bool CppMetricsParserTest::queryRecordMetric( // McCabe -typedef std::pair McCabeParam; - class ParameterizedMcCabeTest : public CppMetricsParserTest, - public ::testing::WithParamInterface + public ::testing::WithParamInterface {}; -std::vector paramMcCabe = { +std::vector paramMcCabe = { {"conditionals", 8}, {"loops1", 6}, {"loops2", 6}, @@ -173,10 +175,10 @@ INSTANTIATE_TEST_SUITE_P( class ParameterizedTypeMcCabeTest : public CppMetricsParserTest, - public ::testing::WithParamInterface + public ::testing::WithParamInterface {}; -std::vector paramTypeMcCabe = { +std::vector paramTypeMcCabe = { {"Empty", 0}, {"ClassMethodsInside", 16}, {"ClassMethodsOutside", 44}, @@ -271,13 +273,12 @@ INSTANTIATE_TEST_SUITE_P( // Afferent coupling -typedef std::pair AfferentParam; class ParameterizedAfferentCouplingTest : public CppMetricsParserTest, - public ::testing::WithParamInterface + public ::testing::WithParamInterface {}; -std::vector paramAfferent = { +std::vector paramAfferent = { {"A", 1}, {"A2", 1}, {"A3", 1}, @@ -320,3 +321,35 @@ INSTANTIATE_TEST_SUITE_P( ParameterizedAfferentCouplingTest, ::testing::ValuesIn(paramAfferent) ); + +// Efferent coupling at module level + +class ParameterizedEfferentModuleCouplingTest + : public CppMetricsParserTest, + public ::testing::WithParamInterface +{}; + +std::vector paramEfferentModule = { + {"%/test/sources/parser/module_a", 1}, + {"%/test/sources/parser/module_b", 0}, + {"%/test/sources/parser/module_c", 2}, +}; + +TEST_P(ParameterizedEfferentModuleCouplingTest, ModuleEfferentTest) { + _transaction([&, this]() { + + typedef odb::query CppModuleMetricsQuery; + + const auto metric = _db->query_value( + CppModuleMetricsQuery::CppFileMetrics::type == model::CppFileMetrics::Type::EFFERENT_MODULE && + CppModuleMetricsQuery::File::path.like(GetParam().first)); + + EXPECT_EQ(GetParam().second, metric.value); + }); +} + +INSTANTIATE_TEST_SUITE_P( + ParameterizedEfferentModuleCouplingTestSuite, + ParameterizedEfferentModuleCouplingTest, + ::testing::ValuesIn(paramEfferentModule) +); diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index 7bf221ae3..b8cbabe2e 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -18,6 +18,7 @@ add_library(util SHARED src/util.cpp) target_link_libraries(util + model gvc ${Boost_LIBRARIES}) diff --git a/util/include/util/dbutil.h b/util/include/util/dbutil.h index e48ddae30..a5ed30fd6 100644 --- a/util/include/util/dbutil.h +++ b/util/include/util/dbutil.h @@ -3,6 +3,8 @@ #include #include +#include +#include #include @@ -147,10 +149,15 @@ odb::query getFilterPathsQuery( TIter begin_, const TSentinel& end_) { - typedef typename odb::query::query_columns QParam; - const auto& QParamPath = QParam::File::path; - constexpr char ODBWildcard = '%'; + const auto& QParamPath = [](){ + if constexpr (std::is_same::value) { + return odb::query::path; + } else { + return odb::query::query_columns::File::path; + } + }(); + constexpr char ODBWildcard = '%'; assert(begin_ != end_ && "At least one filter path must be provided."); odb::query query = QParamPath.like(*begin_ + ODBWildcard); while (++begin_ != end_)