Skip to content

Commit 2acf14b

Browse files
committed
Add code-maat class metrics to risk calculation - WIP
1 parent 727c4f7 commit 2acf14b

10 files changed

Lines changed: 180 additions & 28 deletions

File tree

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<commons-io.version>2.18.0</commons-io.version>
4444
<annotations.version>26.0.2</annotations.version>
4545
<ck.version>0.7.0</ck.version>
46+
<commons-collections4.version>4.4</commons-collections4.version>
4647

4748
<!-- test -->
4849
<maven-plugin-testing-harness.version>3.3.0</maven-plugin-testing-harness.version>
@@ -126,6 +127,11 @@
126127
</exclusion>
127128
</exclusions>
128129
</dependency>
130+
<dependency>
131+
<groupId>org.apache.commons</groupId>
132+
<artifactId>commons-collections4</artifactId>
133+
<version>${commons-collections4.version}</version>
134+
</dependency>
129135

130136
<!-- test -->
131137
<dependency>

src/main/java/at/aau/metrics/CkMetricCollector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ private CkMetricCollector() {
2727
throw new UnsupportedOperationException("Utility class");
2828
}
2929

30-
public static List<MetricsData> collectMetrics(Path path) {
30+
public static List<MetricsData> collectCkMetrics(Path path) {
3131
if (!PathHelper.isValidDirectory(path)) {
3232
return Collections.emptyList();
3333
}

src/main/java/at/aau/metrics/MethodMatcher.java

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package at.aau.metrics;
22

3+
import java.io.IOException;
4+
import java.nio.file.Files;
5+
import java.nio.file.Path;
36
import java.util.ArrayList;
47
import java.util.Arrays;
8+
import java.util.Collections;
59
import java.util.List;
610
import java.util.Optional;
711
import java.util.stream.Collectors;
@@ -14,11 +18,18 @@
1418

1519
import at.aau.jacoco.model.Method;
1620
import at.aau.model.MethodDescriptor;
21+
import at.aau.model.MetricMeasurement;
22+
import at.aau.model.MetricType;
23+
import at.aau.model.MetricsData;
1724

18-
public class MethodMatcher {
25+
public final class MethodMatcher {
1926

2027
private static final Logger log = LoggerFactory.getLogger(MethodMatcher.class);
2128

29+
private MethodMatcher() {
30+
throw new UnsupportedOperationException("Utility class");
31+
}
32+
2233
public static Optional<MethodDescriptor> methodDescriptorFromCkResult(
2334
CKMethodResult ckMethodResult
2435
) {
@@ -30,7 +41,6 @@ public static Optional<MethodDescriptor> methodDescriptorFromCkResult(
3041
) {
3142
if (StringUtils.isBlank(qualifiedMethodName)) {
3243
log.error("Qualified method name is blank; return empty");
33-
3444
return Optional.empty();
3545
}
3646

@@ -42,9 +52,7 @@ public static Optional<MethodDescriptor> methodDescriptorFromCkResult(
4252
String[] parts = StringUtils.split(qualifiedMethodName, '/');
4353

4454
if (parts.length != 2) {
45-
log.error(
46-
"Qualified method name is malformed; return empty - [name={}]", qualifiedMethodName);
47-
55+
log.error("Qualified method name is malformed; return empty - [name={}]", qualifiedMethodName);
4856
return Optional.empty();
4957
}
5058

@@ -67,10 +75,12 @@ public static Optional<MethodDescriptor> methodDescriptorFromJacoco(Method metho
6775
}
6876

6977
public static Optional<MethodDescriptor> methodDescriptorFromJacoco(
70-
String className, String methodName, String methodDesc) {
78+
String className,
79+
String methodName,
80+
String methodDesc
81+
) {
7182
if (StringUtils.isAnyBlank(className, methodName, methodDesc)) {
7283
log.error("Any parameter is blank; return empty");
73-
7484
return Optional.empty();
7585
}
7686

@@ -79,14 +89,60 @@ public static Optional<MethodDescriptor> methodDescriptorFromJacoco(
7989
List<String> parameters = new ArrayList<>();
8090

8191
if (StringUtils.isNoneBlank(parameterString)) {
82-
parameters =
83-
Arrays.stream(parameterString.split(";"))
84-
.map(MetricUtils::normalizeClassName)
85-
.map(p -> p.substring(1))
86-
.collect(Collectors.toList());
92+
parameters = Arrays.stream(parameterString.split(";"))
93+
.map(MetricUtils::normalizeClassName)
94+
.map(p -> p.substring(1))
95+
.collect(Collectors.toList());
8796
}
8897

8998
return Optional.of(MethodDescriptor.of(normalizedClassName, methodName, parameters));
9099
}
91100

101+
public static List<MetricsData> methodDescriptorFromMaat(Path maatPath, MetricType maatMetricType) {
102+
List<String> maatLines;
103+
104+
try {
105+
maatLines = Files.readAllLines(maatPath);
106+
} catch (IOException e) {
107+
log.error("Error reading lines from maat path", e);
108+
return Collections.emptyList();
109+
}
110+
111+
boolean firstLine = true;
112+
List<MetricsData> metrics = new ArrayList<>();
113+
114+
for (String maatLine : maatLines) {
115+
if (firstLine) {
116+
firstLine = false;
117+
continue;
118+
}
119+
120+
String[] maatParts = StringUtils.split(maatLine, ',');
121+
122+
if (maatParts.length != 2) {
123+
log.error("Invalid maat line; return empty - [line='{}']", maatLine);
124+
return Collections.emptyList();
125+
}
126+
127+
String fullFilePath = maatParts[0];
128+
String metric = maatParts[1];
129+
130+
String fqn = StringUtils.substringBetween(fullFilePath, "src/main/java/", ".java");
131+
132+
if (StringUtils.isBlank(fqn)) {
133+
log.info("Invalid line or just not a java file; skip - [line={}]", maatLine);
134+
continue;
135+
}
136+
137+
String nFqn = MetricUtils.normalizeClassName(fqn);
138+
139+
MetricMeasurement metricMeasurement = MetricMeasurement.of(maatMetricType, Double.parseDouble(metric));
140+
MetricsData metricsData = MetricsData.of(nFqn, List.of(metricMeasurement));
141+
142+
metrics.add(metricsData);
143+
}
144+
145+
return metrics;
146+
}
147+
92148
}

src/main/java/at/aau/metrics/RiskMetricCalculator.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.Map;
88
import java.util.stream.Collectors;
99

10+
import at.aau.model.ClassMetricType;
1011
import at.aau.model.MethodMetricType;
1112
import at.aau.model.MethodWithRisk;
1213
import at.aau.model.MetricMeasurement;
@@ -107,20 +108,29 @@ private static MethodWithRisk calculateRiskScore(MetricsData normalizedMetricsDa
107108
normalizedMetricsData.getMethodMetrics().stream()
108109
.collect(Collectors.toMap(MetricMeasurement::getType, MetricMeasurement::getValue));
109110

111+
double classRiskValue =
112+
(0.3 * classMetricsNameToValueMap.get(ClassMetricType.REVS))
113+
+ (0.2 * classMetricsNameToValueMap.get(ClassMetricType.LOC))
114+
+ (0.2 * classMetricsNameToValueMap.get(ClassMetricType.NOM))
115+
+ (0.2 * classMetricsNameToValueMap.get(ClassMetricType.WMC))
116+
+ (0.1 * classMetricsNameToValueMap.get(ClassMetricType.CBO));
117+
110118
double W1 = 0.3;
111119
double W2 = 0.2;
112120
double W3 = 0.2;
113121
double W4 = 0.2;
114122
double W5 = 0.1;
115123

116-
double riskValue =
124+
double methodRiskValue =
117125
(W1 * methodMetricsNameToValueMap.get(MethodMetricType.WMC))
118126
+ (W2 * methodMetricsNameToValueMap.get(MethodMetricType.CBO))
119127
+ (W3 * methodMetricsNameToValueMap.get(MethodMetricType.RFC))
120128
+ (W4 * methodMetricsNameToValueMap.get(MethodMetricType.FOUT))
121129
+ (W5 * methodMetricsNameToValueMap.get(MethodMetricType.FIN));
122130

123-
return MethodWithRisk.of(normalizedMetricsData.getMethodDescriptor(), riskValue);
131+
double weightedMethodRisk = (0.3 * classRiskValue) + (0.7 * methodRiskValue);
132+
133+
return MethodWithRisk.of(normalizedMetricsData.getMethodDescriptor(), weightedMethodRisk);
124134
}
125135

126136
}

src/main/java/at/aau/model/ClassMetricType.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ public enum ClassMetricType implements MetricType {
1111
NOC,
1212
NOM,
1313
CBO,
14-
RFC;
14+
RFC,
15+
SOC,
16+
REVS;
1517

1618
@Override
1719
public String getName() {

src/main/java/at/aau/model/MetricsData.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55

66
import com.google.common.base.MoreObjects;
77

8-
import at.aau.util.ListUtils;
9-
108
public class MetricsData {
119

1210
private final MethodDescriptor methodDescriptor;
@@ -19,8 +17,15 @@ private MetricsData(
1917
List<MetricMeasurement> classMetrics
2018
) {
2119
this.methodDescriptor = methodDescriptor;
22-
this.methodMetrics = ListUtils.unmodifiableList(methodMetrics);
23-
this.classMetrics = ListUtils.unmodifiableList(classMetrics);
20+
this.methodMetrics = methodMetrics;
21+
this.classMetrics = classMetrics;
22+
}
23+
24+
public static MetricsData of(
25+
String className,
26+
List<MetricMeasurement> classMetrics
27+
) {
28+
return of(className, null, null, null, classMetrics);
2429
}
2530

2631
public static MetricsData of(
@@ -42,6 +47,14 @@ public static MetricsData of(
4247
return new MetricsData(methodDescriptor, methodMetrics, classMetrics);
4348
}
4449

50+
public void addClassMetrics(List<MetricMeasurement> classMetrics) {
51+
if (this.classMetrics == null) {
52+
return;
53+
}
54+
55+
this.classMetrics.addAll(classMetrics);
56+
}
57+
4558
public String getClassName() {
4659
return methodDescriptor.getClassName();
4760
}

src/main/java/at/aau/mojo/TestGapScannerMojo.java

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,44 @@
55
import java.nio.file.Files;
66
import java.nio.file.Path;
77
import java.util.List;
8+
import java.util.Map;
9+
import java.util.stream.Collectors;
810

11+
import org.apache.commons.collections4.CollectionUtils;
912
import org.apache.maven.execution.MavenSession;
1013
import org.apache.maven.plugin.AbstractMojo;
1114
import org.apache.maven.plugins.annotations.Mojo;
1215
import org.apache.maven.plugins.annotations.Parameter;
1316
import org.apache.maven.project.MavenProject;
17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
1419

1520
import at.aau.jacoco.JacocoCoverageCollector;
1621
import at.aau.jacoco.JacocoCoverageFilter;
1722
import at.aau.jacoco.model.Method;
1823
import at.aau.jacoco.model.Package;
1924
import at.aau.metrics.CkMetricCollector;
25+
import at.aau.metrics.MethodMatcher;
2026
import at.aau.metrics.MetricUtils;
2127
import at.aau.metrics.RiskMetricCalculator;
28+
import at.aau.model.ClassMetricType;
2229
import at.aau.model.MethodWithRisk;
2330
import at.aau.model.MetricsData;
2431

2532
@Mojo(name = "test-gap-scanner")
2633
public class TestGapScannerMojo extends AbstractMojo {
2734

35+
private static final Logger log = LoggerFactory.getLogger(CkMetricCollector.class);
36+
2837
@Parameter(defaultValue = "${project.build.directory}/site/jacoco/jacoco.xml")
2938
private String coverageReportPath;
3039

40+
@Parameter(defaultValue = "${project.build.directory}/site/code-maat/revisions.csv")
41+
private String maatRevisionsPath;
42+
43+
@Parameter(defaultValue = "${project.build.directory}/site/code-maat/soc.csv")
44+
private String maatSocPath;
45+
3146
@Parameter(defaultValue = "${project}", readonly = true, required = true)
3247
private MavenProject project;
3348

@@ -44,14 +59,28 @@ private static List<Method> getUntestedMethods(Path coverageReport) throws Excep
4459
public void execute() {
4560
Path projectBaseDirPath = project.getBasedir().toPath();
4661
Path coverageReport = Path.of(coverageReportPath);
62+
Path maatRevisions = Path.of(maatRevisionsPath);
63+
Path maatSoc = Path.of(maatSocPath);
4764

4865
if (Files.notExists(coverageReport)) {
49-
getLog().warn("Coverage report not found at " + coverageReport);
66+
getLog().warn("Coverage report not found at " + coverageReportPath);
67+
68+
return;
69+
}
70+
71+
if (Files.notExists(maatRevisions)) {
72+
getLog().warn("Maat revisions report not found at " + maatRevisionsPath);
5073

5174
return;
5275
}
5376

54-
getLog().info("Valid coverage report found, processing...");
77+
if (Files.notExists(maatSoc)) {
78+
getLog().warn("Maat SoC report not found at " + maatSocPath);
79+
80+
return;
81+
}
82+
83+
getLog().info("Valid reports found, processing...");
5584

5685
List<Method> uncoveredMethods;
5786

@@ -64,11 +93,26 @@ public void execute() {
6493

6594
getLog().info("Calculating risk scores for untested classes...");
6695

67-
List<MetricsData> methodMetricsData = CkMetricCollector.collectMetrics(projectBaseDirPath);
96+
List<MetricsData> methodMetricsData = CkMetricCollector.collectCkMetrics(projectBaseDirPath);
6897
List<MetricsData> untestedMethodsMetricsData =
6998
MetricUtils.getUntestedMethodMetrics(uncoveredMethods, methodMetricsData);
70-
List<MetricsData> normalizedMetricsData =
71-
RiskMetricCalculator.normalizeMetricsData(untestedMethodsMetricsData);
99+
100+
List<MetricsData> metricsData = MethodMatcher.methodDescriptorFromMaat(maatRevisions, ClassMetricType.REVS);
101+
102+
Map<String, List<MetricsData>> classNameToMetricsDataMap = untestedMethodsMetricsData.stream()
103+
.collect(Collectors.groupingBy(MetricsData::getClassName, Collectors.toList()));
104+
105+
for (MetricsData metricData : metricsData) {
106+
List<MetricsData> mapEntries = classNameToMetricsDataMap.get(metricData.getClassName());
107+
108+
if (CollectionUtils.isNotEmpty(mapEntries)) {
109+
MetricsData firstEntry = mapEntries.get(0);
110+
111+
firstEntry.addClassMetrics(metricData.getClassMetrics());
112+
}
113+
}
114+
115+
List<MetricsData> normalizedMetricsData = RiskMetricCalculator.normalizeMetricsData(untestedMethodsMetricsData);
72116
List<MethodWithRisk> descendingMethodRiskScores =
73117
RiskMetricCalculator.getDescendingMethodRiskScores(normalizedMetricsData);
74118

@@ -80,8 +124,7 @@ public void execute() {
80124

81125
String rank = String.format("%d:", i + 1);
82126
String descriptor = String.format(" FQN: %s", matchingClass.getMethodDescriptor());
83-
String symbol =
84-
String.format(" Symbol: %s", matchingClass.getMethodDescriptor().getSymbol());
127+
String symbol = String.format(" Symbol: %s", matchingClass.getMethodDescriptor().getSymbol());
85128
String risk = String.format(" Risk: %.2f", matchingClass.getRisk());
86129

87130
outputBuilder.append(rank).append(System.lineSeparator());
@@ -96,6 +139,8 @@ public void execute() {
96139

97140
private void writeToFile(Path filePath, String content) {
98141
try {
142+
log.info("Writing report to file: '{}'", filePath);
143+
99144
Files.writeString(filePath, content, StandardCharsets.UTF_8);
100145
} catch (IOException e) {
101146
getLog().error("Error writing content to file; abort - [filePath='{}']" + filePath, e);

src/test/java/at/aau/metrics/CkMetricCollectorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class CkMetricCollectorTest {
55
// @Test
66
// void shouldReturnClassMetrics_whenPathIsValid() {
77
// var classPath = Paths.get("/home/timo/Development/plincs/basf");
8-
// var metrics = CkMetricCollector.collectMetrics(classPath);
8+
// var metrics = CkMetricCollector.collectCkMetrics(classPath);
99
//
1010
// var classWithRisks =
1111
// RiskMetricCalculator.getNormalizedClassMetrics(metrics).stream()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
entity,n-revs
2+
src/main/java/at/aau/testproject/management/DocumentManager.java,40
3+
src/main/java/at/aau/testproject/management/ReportGenerator.java,34
4+
src/main/java/at/aau/testproject/TextDocument.java,22
5+
src/main/java/at/aau/testproject/SimpleFormatter.java,17
6+
src/main/java/at/aau/testproject/Document.java,12
7+
src/main/java/at/aau/testproject/SpreadsheetDocument.java,5
8+
src/main/java/at/aau/testproject/ComplexFormatter.java,1
9+
src/main/java/at/aau/testproject/Formatter.java,1
10+
src/main/java/at/aau/testproject/Printer.java,1

0 commit comments

Comments
 (0)