diff --git a/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h b/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h index 2e4ce93eb..b77cca36e 100644 --- a/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h +++ b/plugins/cpp_metrics/model/include/model/cppcohesionmetrics.h @@ -25,6 +25,16 @@ struct CohesionCppRecordView CppAstNodeId astNodeId; }; +#pragma db view \ + object(CppRecord) \ + object(CppAstNode : CppRecord::astNodeId == CppAstNode::id) \ + object(File : CppAstNode::location.file) +struct CohesionCppRecord_Count +{ + #pragma db column("count(" + CppEntity::id + ")") + std::size_t count; +}; + #pragma db view \ object(CppMemberType) \ object(CppAstNode : CppMemberType::memberAstNode) \ diff --git a/plugins/cpp_metrics/model/include/model/cppfilemetrics.h b/plugins/cpp_metrics/model/include/model/cppfilemetrics.h index faa977aa0..1edfa6740 100644 --- a/plugins/cpp_metrics/model/include/model/cppfilemetrics.h +++ b/plugins/cpp_metrics/model/include/model/cppfilemetrics.h @@ -13,8 +13,9 @@ struct CppFileMetrics { enum Type { - EFFERENT_MODULE, - AFFERENT_MODULE + EFFERENT_MODULE = 1, + AFFERENT_MODULE = 2, + RELATIONAL_COHESION_MODULE = 3 }; #pragma db id auto @@ -27,7 +28,7 @@ struct CppFileMetrics Type type; #pragma db not_null - unsigned value; + double value; }; #pragma db view \ @@ -45,7 +46,7 @@ struct CppModuleMetricsForPathView CppFileMetrics::Type type; #pragma db column(CppFileMetrics::value) - unsigned value; + double value; }; #pragma db view \ diff --git a/plugins/cpp_metrics/model/include/model/cpptypedependencymetrics.h b/plugins/cpp_metrics/model/include/model/cpptypedependencymetrics.h index 76673904c..e1c64dbe8 100644 --- a/plugins/cpp_metrics/model/include/model/cpptypedependencymetrics.h +++ b/plugins/cpp_metrics/model/include/model/cpptypedependencymetrics.h @@ -49,6 +49,20 @@ struct CppTypeDependencyMetricsPathView 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 CppTypeDependencyMetrics_Count +{ + #pragma db column("count(" + CppTypeDependencyMetrics::id + ")") + std::size_t count; +}; + #pragma db view \ object(CppTypeDependencyMetrics) \ object(CppAstNode = EntityAstNode : CppTypeDependencyMetrics::entityHash == EntityAstNode::entityHash \ diff --git a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h index 35f20440e..9fabefd1c 100644 --- a/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h +++ b/plugins/cpp_metrics/parser/include/cppmetricsparser/cppmetricsparser.h @@ -84,6 +84,8 @@ class CppMetricsParser : public AbstractParser void efferentModuleLevel(); // Calculate the afferent coupling at module level. void afferentModuleLevel(); + // Calculate relational cohesion at module level. + void relationalCohesionModuleLevel(); // Returns module path query based on parser configuration. odb::query getModulePathsQuery(); @@ -210,6 +212,7 @@ class CppMetricsParser : public AbstractParser static const int afferentCouplingTypesPartitionMultiplier = 5; static const int efferentCouplingModulesPartitionMultiplier = 5; static const int afferentCouplingModulesPartitionMultiplier = 5; + static const int relationalCohesionPartitionMultiplier = 5; }; } // parser diff --git a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp index 3edbca7a1..c949be7a2 100644 --- a/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp +++ b/plugins/cpp_metrics/parser/src/cppmetricsparser.cpp @@ -383,8 +383,6 @@ void CppMetricsParser::efferentTypeLevel() { typedef odb::query MemTypeQuery; typedef odb::query InheritanceQuery; - typedef odb::query ParamQuery; - typedef odb::query LocalQuery; typedef odb::query FuncQuery; std::set dependentTypes; @@ -605,6 +603,53 @@ void CppMetricsParser::afferentModuleLevel() }); } +void CppMetricsParser::relationalCohesionModuleLevel() +{ + // Compute relational cohesion defined by CppDepend: + // https://www.cppdepend.com/documentation/code-metrics#RelationalCohesion + + parallelCalcMetric( + "Relational cohesion at module level", + _threadCount * relationalCohesionPartitionMultiplier, // number of jobs; adjust for granularity + getModulePathsQuery(), + [&, this](const MetricsTasks& tasks) + { + util::OdbTransaction{_ctx.db}([&, this] + { + typedef odb::query TypeDependencyQuery; + typedef model::CppTypeDependencyMetrics_Count TypeDependencyResult; + typedef odb::query RecordQuery; + typedef model::CohesionCppRecord_Count RecordResult; + + for (const model::File& file : tasks) + { + TypeDependencyResult dependencies = _ctx.db->query_value( + TypeDependencyQuery::EntityFile::path.like(file.path + '%') && + TypeDependencyQuery::DependencyFile::path.like(file.path + '%')); + + // Let R be the number of type relationships that are internal to a module + // (i.e that do not connect to types outside the module) + const std::size_t r = dependencies.count; + + RecordResult types = _ctx.db->query_value( + RecordQuery::File::path.like(file.path + '%')); + + // Let N be the number of types within the module. + const std::size_t n = types.count; + + // Relational cohesion + const double h = (n != 0) ? (double)(r + 1) / n : 0; + + model::CppFileMetrics metric; + metric.file = file.id; + metric.type = model::CppFileMetrics::Type::RELATIONAL_COHESION_MODULE; + metric.value = h; + _ctx.db->persist(metric); + } + }); + }); +} + bool CppMetricsParser::parse() { LOG(info) << "[cppmetricsparser] Computing function parameter count metric."; @@ -625,6 +670,8 @@ bool CppMetricsParser::parse() efferentModuleLevel(); // This metric needs to be calculated after efferentTypeLevel LOG(info) << "[cppmetricsparser] Computing afferent coupling metric at module level."; afferentModuleLevel(); // This metric needs to be calculated after afferentTypeLevel + LOG(info) << "[cppmetricsparser] Computing relational cohesion metric at module level."; + relationalCohesionModuleLevel(); // This metric needs to be calculated after efferentTypeLevel return true; } diff --git a/plugins/cpp_metrics/service/cxxmetrics.thrift b/plugins/cpp_metrics/service/cxxmetrics.thrift index 08975f80e..65f3f383e 100644 --- a/plugins/cpp_metrics/service/cxxmetrics.thrift +++ b/plugins/cpp_metrics/service/cxxmetrics.thrift @@ -18,7 +18,9 @@ enum CppAstNodeMetricsType enum CppModuleMetricsType { - Placeholder = 1 + EfferentModule = 1, + AfferentModule = 2, + RelationalCohesionModule = 3 } struct CppAstNodeMetricsTypeName diff --git a/plugins/cpp_metrics/service/src/cppmetricsservice.cpp b/plugins/cpp_metrics/service/src/cppmetricsservice.cpp index 12785856e..a9f5a46dd 100644 --- a/plugins/cpp_metrics/service/src/cppmetricsservice.cpp +++ b/plugins/cpp_metrics/service/src/cppmetricsservice.cpp @@ -70,8 +70,16 @@ void CppMetricsServiceHandler::getCppModuleMetricsTypeNames( { CppModuleMetricsTypeName typeName; - typeName.type = CppModuleMetricsType::Placeholder; - typeName.name = "Placeholder"; + typeName.type = CppModuleMetricsType::EfferentModule; + typeName.name = "Efferent coupling of module"; + _return.push_back(typeName); + + typeName.type = CppModuleMetricsType::AfferentModule; + typeName.name = "Afferent coupling of module"; + _return.push_back(typeName); + + typeName.type = CppModuleMetricsType::RelationalCohesionModule; + typeName.name = "Relational cohesion of module"; _return.push_back(typeName); } diff --git a/plugins/cpp_metrics/test/sources/parser/modulemetrics.cpp b/plugins/cpp_metrics/test/sources/parser/modulemetrics.cpp index 5080981b8..5ba196827 100644 --- a/plugins/cpp_metrics/test/sources/parser/modulemetrics.cpp +++ b/plugins/cpp_metrics/test/sources/parser/modulemetrics.cpp @@ -5,3 +5,12 @@ #include "./module_c/c1.h" #include "./module_c/c2.h" #include "./module_d/d1.h" + +#include "./rc_module_a/a1.h" +#include "./rc_module_a/a2.h" +#include "./rc_module_a/a3.h" +#include "./rc_module_b/b1.h" +#include "./rc_module_c/c1.h" +#include "./rc_module_c/c2.h" +#include "./rc_module_c/c3.h" +#include "./rc_module_c/c4.h" diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_a/a1.h b/plugins/cpp_metrics/test/sources/parser/rc_module_a/a1.h new file mode 100644 index 000000000..5899897e6 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_a/a1.h @@ -0,0 +1,15 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_A1 +#define CC_CPP_RC_MODULE_METRICS_TEST_A1 + +#include "./a2.h" +#include "./a3.h" + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class A1 { + A2 a2; + A3 a3; +}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_a/a2.h b/plugins/cpp_metrics/test/sources/parser/rc_module_a/a2.h new file mode 100644 index 000000000..60a21f10f --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_a/a2.h @@ -0,0 +1,13 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_A2 +#define CC_CPP_RC_MODULE_METRICS_TEST_A2 + +#include "./a3.h" + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class A2 { + A3 a3; +}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_a/a3.h b/plugins/cpp_metrics/test/sources/parser/rc_module_a/a3.h new file mode 100644 index 000000000..8365c3615 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_a/a3.h @@ -0,0 +1,9 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_A3 +#define CC_CPP_RC_MODULE_METRICS_TEST_A3 + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class A3 {}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_b/b1.h b/plugins/cpp_metrics/test/sources/parser/rc_module_b/b1.h new file mode 100644 index 000000000..e432443bc --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_b/b1.h @@ -0,0 +1,9 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_B1 +#define CC_CPP_RC_MODULE_METRICS_TEST_B1 + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class B1 {}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_c/c1.h b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c1.h new file mode 100644 index 000000000..8f7cadd5f --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c1.h @@ -0,0 +1,17 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_C1 +#define CC_CPP_RC_MODULE_METRICS_TEST_C1 + +#include "../rc_module_a/a1.h" +#include "../rc_module_a/a2.h" +#include "../rc_module_a/a3.h" + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class C1 { + A1 a1; + A2 a2; + A3 a3; +}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_c/c2.h b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c2.h new file mode 100644 index 000000000..bb99cb474 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c2.h @@ -0,0 +1,13 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_C2 +#define CC_CPP_RC_MODULE_METRICS_TEST_C2 + +#include "./c1.h" + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class C2 { + C1 c1; +}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_c/c3.h b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c3.h new file mode 100644 index 000000000..4412a9913 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c3.h @@ -0,0 +1,9 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_C3 +#define CC_CPP_RC_MODULE_METRICS_TEST_C3 + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class C3 {}; +} + +#endif diff --git a/plugins/cpp_metrics/test/sources/parser/rc_module_c/c4.h b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c4.h new file mode 100644 index 000000000..5e0467aa9 --- /dev/null +++ b/plugins/cpp_metrics/test/sources/parser/rc_module_c/c4.h @@ -0,0 +1,9 @@ +#ifndef CC_CPP_RC_MODULE_METRICS_TEST_C4 +#define CC_CPP_RC_MODULE_METRICS_TEST_C4 + +namespace CC_CPP_RC_MODULE_METRICS_TEST +{ +class C4 {}; +} + +#endif diff --git a/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp b/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp index 095c40d54..fb9a18a51 100644 --- a/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp +++ b/plugins/cpp_metrics/test/src/cppmetricsparsertest.cpp @@ -24,6 +24,7 @@ using namespace cc; extern char* dbConnectionString; typedef std::pair StringUintParam; +typedef std::pair StringDoubleParam; class CppMetricsParserTest : public ::testing::Test { @@ -387,3 +388,41 @@ INSTANTIATE_TEST_SUITE_P( ParameterizedAfferentModuleCouplingTest, ::testing::ValuesIn(paramAfferentModule) ); + +// Relational cohesion at module level + +class ParameterizedRelationalCohesionTest + : public CppMetricsParserTest, + public ::testing::WithParamInterface +{}; + +// Relational cohesion formula: +// H = (R + 1)/ N +// where R is the number of type relationships that are internal to a module, +// N is the number of types within a module. + +std::vector paramRelationalCohesion = { + {"%/test/sources/parser/rc_module_a", (3 + 1) / 3.0}, + {"%/test/sources/parser/rc_module_b", (0 + 1) / 1.0}, + {"%/test/sources/parser/rc_module_c", (1 + 1) / 4.0}, +}; + +TEST_P(ParameterizedRelationalCohesionTest, RelationalCohesionTest) { + _transaction([&, this]() { + + typedef odb::query CppModuleMetricsQuery; + + const auto metric = _db->query_value( + CppModuleMetricsQuery::CppFileMetrics::type == model::CppFileMetrics::Type::RELATIONAL_COHESION_MODULE && + CppModuleMetricsQuery::File::path.like(GetParam().first)); + + constexpr double tolerance = 1e-8; + EXPECT_NEAR(GetParam().second, metric.value, tolerance); + }); +} + +INSTANTIATE_TEST_SUITE_P( + ParameterizedRelationalCohesionTestSuite, + ParameterizedRelationalCohesionTest, + ::testing::ValuesIn(paramRelationalCohesion) +); \ No newline at end of file