Skip to content

Commit 89c6d63

Browse files
Implement sending total coverage percentage for sessions and modules (DataDog#5769)
1 parent f3d23bd commit 89c6d63

File tree

39 files changed

+640
-131
lines changed

39 files changed

+640
-131
lines changed

dd-java-agent/agent-ci-visibility/build.gradle

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ apply from: "$rootDir/gradle/java.gradle"
77
apply from: "$rootDir/gradle/version.gradle"
88

99
minimumBranchCoverage = 0.7
10+
minimumInstructionCoverage = 0.8
11+
1012
excludedClassesCoverage += [
1113
"datadog.trace.civisibility.CiVisibilitySystem",
1214
"datadog.trace.civisibility.CiVisibilitySystem.1",
@@ -38,14 +40,16 @@ excludedClassesCoverage += [
3840
"datadog.trace.civisibility.context.EmptyTestContext",
3941
"datadog.trace.civisibility.context.ParentProcessTestContext",
4042
"datadog.trace.civisibility.context.SpanTestContext",
41-
"datadog.trace.civisibility.coverage.TestProbes.TestProbesFactory",
43+
"datadog.trace.civisibility.coverage.CoverageUtils",
44+
"datadog.trace.civisibility.coverage.CoverageUtils.RepoIndexFileLocator",
4245
"datadog.trace.civisibility.coverage.ExecutionDataAdapter",
4346
"datadog.trace.civisibility.coverage.NoopCoverageProbeStore",
4447
"datadog.trace.civisibility.coverage.NoopCoverageProbeStore.NoopCoverageProbeStoreFactory",
4548
"datadog.trace.civisibility.coverage.SegmentlessTestProbes",
4649
"datadog.trace.civisibility.coverage.SegmentlessTestProbes.SegmentlessTestProbesFactory",
4750
"datadog.trace.civisibility.coverage.SourceAnalyzer",
4851
"datadog.trace.civisibility.coverage.TestProbes",
52+
"datadog.trace.civisibility.coverage.TestProbes.TestProbesFactory",
4953
"datadog.trace.civisibility.events.BuildEventsHandlerImpl",
5054
"datadog.trace.civisibility.events.TestEventsHandlerImpl",
5155
"datadog.trace.civisibility.events.TestDescriptor",
@@ -69,15 +73,14 @@ excludedClassesCoverage += [
6973
"datadog.trace.civisibility.utils.ShellCommandExecutor",
7074
"datadog.trace.civisibility.utils.ShellCommandExecutor.OutputParser",
7175
]
72-
minimumInstructionCoverage = 0.8
73-
7476

7577
dependencies {
7678
api deps.slf4j
7779

7880
implementation deps.asm
7981
implementation deps.asmcommons
8082
implementation group: 'org.jacoco', name: 'org.jacoco.core', version: '0.8.9'
83+
implementation group: 'org.jacoco', name: 'org.jacoco.report', version: '0.8.9'
8184

8285
implementation project(':communication')
8386
implementation project(':internal-api')

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import datadog.trace.api.Config;
55
import datadog.trace.api.civisibility.CIVisibility;
66
import datadog.trace.api.civisibility.InstrumentationBridge;
7+
import datadog.trace.api.civisibility.coverage.CoverageDataSupplier;
78
import datadog.trace.api.civisibility.events.BuildEventsHandler;
89
import datadog.trace.api.civisibility.events.TestEventsHandler;
910
import datadog.trace.api.config.CiVisibilityConfig;
@@ -188,6 +189,7 @@ private static DDTestSessionImpl.SessionImplFactory sessionFactory(
188189
RepoIndexBuilder indexBuilder = new RepoIndexBuilder(repoRoot, FileSystems.getDefault());
189190
return new DDTestSessionParent(
190191
projectName,
192+
repoRoot,
191193
startTime,
192194
config,
193195
testModuleRegistry,
@@ -214,6 +216,7 @@ private static DDTestSessionImpl.SessionImplFactory sessionFactory(
214216
signalServerAddress = new InetSocketAddress(host, Integer.parseInt(port));
215217
}
216218

219+
CoverageDataSupplier coverageDataSupplier = InstrumentationBridge::getCoverageData;
217220
return new DDTestSessionChild(
218221
parentProcessSessionId,
219222
parentProcessModuleId,
@@ -223,6 +226,7 @@ private static DDTestSessionImpl.SessionImplFactory sessionFactory(
223226
codeowners,
224227
methodLinesResolver,
225228
coverageProbeStoreFactory,
229+
coverageDataSupplier,
226230
signalServerAddress);
227231
};
228232
}

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDTestModuleChild.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import datadog.trace.api.Config;
44
import datadog.trace.api.civisibility.config.SkippableTest;
5+
import datadog.trace.api.civisibility.coverage.CoverageDataSupplier;
56
import datadog.trace.bootstrap.instrumentation.api.Tags;
67
import datadog.trace.civisibility.codeowners.Codeowners;
78
import datadog.trace.civisibility.config.JvmInfo;
@@ -30,6 +31,7 @@ public class DDTestModuleChild extends DDTestModuleImpl {
3031
private static final Logger log = LoggerFactory.getLogger(DDTestModuleChild.class);
3132

3233
private final ParentProcessTestContext context;
34+
private final CoverageDataSupplier coverageDataSupplier;
3335

3436
public DDTestModuleChild(
3537
Long parentProcessSessionId,
@@ -41,6 +43,7 @@ public DDTestModuleChild(
4143
Codeowners codeowners,
4244
MethodLinesResolver methodLinesResolver,
4345
CoverageProbeStoreFactory coverageProbeStoreFactory,
46+
CoverageDataSupplier coverageDataSupplier,
4447
@Nullable InetSocketAddress signalServerAddress) {
4548
super(
4649
moduleName,
@@ -52,6 +55,7 @@ public DDTestModuleChild(
5255
coverageProbeStoreFactory,
5356
signalServerAddress);
5457
context = new ParentProcessTestContext(parentProcessSessionId, parentProcessModuleId);
58+
this.coverageDataSupplier = coverageDataSupplier;
5559
}
5660

5761
@Override
@@ -89,6 +93,7 @@ private void sendModuleExecutionResult() {
8993
long testsSkippedTotal = testsSkipped.sum();
9094
String testFramework = String.valueOf(context.getChildTag(Tags.TEST_FRAMEWORK));
9195
String testFrameworkVersion = String.valueOf(context.getChildTag(Tags.TEST_FRAMEWORK_VERSION));
96+
byte[] coverageData = coverageDataSupplier.get();
9297

9398
ModuleExecutionResult moduleExecutionResult =
9499
new ModuleExecutionResult(
@@ -98,7 +103,8 @@ private void sendModuleExecutionResult() {
98103
itrEnabled,
99104
testsSkippedTotal,
100105
testFramework,
101-
testFrameworkVersion);
106+
testFrameworkVersion,
107+
coverageData);
102108

103109
try (SignalClient signalClient = new SignalClient(signalServerAddress)) {
104110
signalClient.send(moduleExecutionResult);

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDTestModuleParent.java

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,22 @@
1717
import datadog.trace.civisibility.context.SpanTestContext;
1818
import datadog.trace.civisibility.context.TestContext;
1919
import datadog.trace.civisibility.coverage.CoverageProbeStoreFactory;
20+
import datadog.trace.civisibility.coverage.CoverageUtils;
2021
import datadog.trace.civisibility.decorator.TestDecorator;
2122
import datadog.trace.civisibility.ipc.ModuleExecutionResult;
2223
import datadog.trace.civisibility.source.MethodLinesResolver;
2324
import datadog.trace.civisibility.source.SourcePathResolver;
25+
import datadog.trace.civisibility.source.index.RepoIndexProvider;
26+
import java.io.File;
2427
import java.net.InetSocketAddress;
28+
import java.nio.file.Paths;
2529
import java.util.Collection;
2630
import java.util.HashSet;
2731
import java.util.Map;
2832
import javax.annotation.Nullable;
33+
import org.jacoco.core.analysis.IBundleCoverage;
34+
import org.jacoco.core.analysis.ICounter;
35+
import org.jacoco.core.data.ExecutionDataStore;
2936

3037
/**
3138
* Representation of a test module in a parent process:
@@ -40,22 +47,27 @@ public class DDTestModuleParent extends DDTestModuleImpl {
4047
private final AgentSpan span;
4148
private final SpanTestContext context;
4249
private final TestContext sessionContext;
43-
private final TestModuleRegistry testModuleRegistry;
50+
private final String repoRoot;
51+
private final Collection<File> outputClassesDirs;
4452
private final ModuleExecutionSettingsFactory moduleExecutionSettingsFactory;
53+
private final RepoIndexProvider repoIndexProvider;
4554
private volatile boolean codeCoverageEnabled;
4655
private volatile boolean itrEnabled;
4756

4857
public DDTestModuleParent(
4958
TestContext sessionContext,
5059
String moduleName,
60+
String repoRoot,
61+
@Nullable String startCommand,
5162
@Nullable Long startTime,
63+
Collection<File> outputClassesDirs,
5264
Config config,
53-
TestModuleRegistry testModuleRegistry,
5465
TestDecorator testDecorator,
5566
SourcePathResolver sourcePathResolver,
5667
Codeowners codeowners,
5768
MethodLinesResolver methodLinesResolver,
5869
ModuleExecutionSettingsFactory moduleExecutionSettingsFactory,
70+
RepoIndexProvider repoIndexProvider,
5971
CoverageProbeStoreFactory coverageProbeStoreFactory,
6072
@Nullable InetSocketAddress signalServerAddress) {
6173
super(
@@ -68,8 +80,10 @@ public DDTestModuleParent(
6880
coverageProbeStoreFactory,
6981
signalServerAddress);
7082
this.sessionContext = sessionContext;
71-
this.testModuleRegistry = testModuleRegistry;
83+
this.repoRoot = repoRoot;
84+
this.outputClassesDirs = outputClassesDirs;
7285
this.moduleExecutionSettingsFactory = moduleExecutionSettingsFactory;
86+
this.repoIndexProvider = repoIndexProvider;
7387

7488
AgentSpan sessionSpan = sessionContext.getSpan();
7589
AgentSpan.Context sessionSpanContext = sessionSpan != null ? sessionSpan.context() : null;
@@ -91,6 +105,10 @@ public DDTestModuleParent(
91105
span.setTag(Tags.TEST_MODULE_ID, context.getId());
92106
span.setTag(Tags.TEST_SESSION_ID, sessionContext.getId());
93107

108+
if (startCommand != null) {
109+
span.setTag(Tags.TEST_COMMAND, startCommand);
110+
}
111+
94112
testDecorator.afterStart(span);
95113
}
96114

@@ -121,8 +139,6 @@ public void setSkipReason(String skipReason) {
121139

122140
@Override
123141
public void end(@Nullable Long endTime) {
124-
testModuleRegistry.removeModule(this);
125-
126142
span.setTag(Tags.TEST_STATUS, context.getStatus());
127143
sessionContext.reportChildStatus(context.getStatus());
128144

@@ -165,7 +181,8 @@ private boolean propertyEnabled(Map<String, String> systemProperties, String pro
165181
return Boolean.parseBoolean(property);
166182
}
167183

168-
public void onModuleExecutionResultReceived(ModuleExecutionResult result) {
184+
public void onModuleExecutionResultReceived(
185+
ModuleExecutionResult result, ExecutionDataStore coverageData) {
169186
codeCoverageEnabled = result.isCoverageEnabled();
170187
itrEnabled = result.isItrEnabled();
171188
testsSkipped.add(result.getTestsSkippedTotal());
@@ -179,5 +196,44 @@ public void onModuleExecutionResultReceived(ModuleExecutionResult result) {
179196
if (testFrameworkVersion != null) {
180197
span.setTag(Tags.TEST_FRAMEWORK_VERSION, testFrameworkVersion);
181198
}
199+
200+
processCoverageData(coverageData);
201+
}
202+
203+
private void processCoverageData(ExecutionDataStore coverageData) {
204+
if (coverageData == null) {
205+
return;
206+
}
207+
IBundleCoverage coverageBundle =
208+
CoverageUtils.createCoverageBundle(coverageData, outputClassesDirs);
209+
if (coverageBundle == null) {
210+
return;
211+
}
212+
213+
long coveragePercentage = getCoveragePercentage(coverageBundle);
214+
span.setTag(Tags.TEST_CODE_COVERAGE_LINES_PERCENTAGE, coveragePercentage);
215+
216+
File coverageReportFolder = getCoverageReportFolder();
217+
if (coverageReportFolder != null) {
218+
CoverageUtils.dumpCoverageReport(
219+
coverageBundle, repoIndexProvider.getIndex(), repoRoot, coverageReportFolder);
220+
}
221+
}
222+
223+
private static long getCoveragePercentage(IBundleCoverage coverageBundle) {
224+
ICounter instructionCounter = coverageBundle.getInstructionCounter();
225+
int totalInstructionsCount = instructionCounter.getTotalCount();
226+
int coveredInstructionsCount = instructionCounter.getCoveredCount();
227+
return Math.round((100d * coveredInstructionsCount) / totalInstructionsCount);
228+
}
229+
230+
private File getCoverageReportFolder() {
231+
String coverageReportDumpDir = config.getCiVisibilityCodeCoverageReportDumpDir();
232+
if (coverageReportDumpDir != null) {
233+
return Paths.get(coverageReportDumpDir, "session-" + context.getParentId(), moduleName)
234+
.toFile();
235+
} else {
236+
return null;
237+
}
182238
}
183239
}

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDTestSessionChild.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
import datadog.trace.api.Config;
44
import datadog.trace.api.civisibility.config.ModuleExecutionSettings;
5+
import datadog.trace.api.civisibility.coverage.CoverageDataSupplier;
56
import datadog.trace.civisibility.codeowners.Codeowners;
67
import datadog.trace.civisibility.config.JvmInfo;
78
import datadog.trace.civisibility.coverage.CoverageProbeStoreFactory;
89
import datadog.trace.civisibility.decorator.TestDecorator;
910
import datadog.trace.civisibility.source.MethodLinesResolver;
1011
import datadog.trace.civisibility.source.SourcePathResolver;
12+
import java.io.File;
1113
import java.net.InetSocketAddress;
14+
import java.util.Collection;
1215
import javax.annotation.Nullable;
1316

1417
public class DDTestSessionChild extends DDTestSessionImpl {
@@ -21,6 +24,7 @@ public class DDTestSessionChild extends DDTestSessionImpl {
2124
private final Codeowners codeowners;
2225
private final MethodLinesResolver methodLinesResolver;
2326
private final CoverageProbeStoreFactory coverageProbeStoreFactory;
27+
private final CoverageDataSupplier coverageDataSupplier;
2428
@Nullable private final InetSocketAddress signalServerAddress;
2529

2630
public DDTestSessionChild(
@@ -32,6 +36,7 @@ public DDTestSessionChild(
3236
Codeowners codeowners,
3337
MethodLinesResolver methodLinesResolver,
3438
CoverageProbeStoreFactory coverageProbeStoreFactory,
39+
CoverageDataSupplier coverageDataSupplier,
3540
@Nullable InetSocketAddress signalServerAddress) {
3641
this.parentProcessSessionId = parentProcessSessionId;
3742
this.parentProcessModuleId = parentProcessModuleId;
@@ -41,6 +46,7 @@ public DDTestSessionChild(
4146
this.codeowners = codeowners;
4247
this.methodLinesResolver = methodLinesResolver;
4348
this.coverageProbeStoreFactory = coverageProbeStoreFactory;
49+
this.coverageDataSupplier = coverageDataSupplier;
4450
this.signalServerAddress = signalServerAddress;
4551
}
4652

@@ -65,7 +71,8 @@ public void end(@Nullable Long endTime) {
6571
}
6672

6773
@Override
68-
public DDTestModuleImpl testModuleStart(String moduleName, @Nullable Long startTime) {
74+
public DDTestModuleImpl testModuleStart(
75+
String moduleName, @Nullable Long startTime, Collection<File> outputClassesDirs) {
6976
return new DDTestModuleChild(
7077
parentProcessSessionId,
7178
parentProcessModuleId,
@@ -76,6 +83,7 @@ public DDTestModuleImpl testModuleStart(String moduleName, @Nullable Long startT
7683
codeowners,
7784
methodLinesResolver,
7885
coverageProbeStoreFactory,
86+
coverageDataSupplier,
7987
signalServerAddress);
8088
}
8189

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/DDTestSessionImpl.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,20 @@
44
import datadog.trace.api.civisibility.DDTestSession;
55
import datadog.trace.api.civisibility.config.ModuleExecutionSettings;
66
import datadog.trace.civisibility.config.JvmInfo;
7+
import java.io.File;
78
import java.nio.file.Path;
9+
import java.util.Collection;
10+
import java.util.Collections;
811
import javax.annotation.Nullable;
912

1013
public abstract class DDTestSessionImpl implements DDTestSession {
1114

12-
public abstract DDTestModuleImpl testModuleStart(String moduleName, @Nullable Long startTime);
15+
public DDTestModuleImpl testModuleStart(String moduleName, @Nullable Long startTime) {
16+
return testModuleStart(moduleName, startTime, Collections.emptyList());
17+
}
18+
19+
public abstract DDTestModuleImpl testModuleStart(
20+
String moduleName, @Nullable Long startTime, Collection<File> outputClassesDirs);
1321

1422
public abstract ModuleExecutionSettings getModuleExecutionSettings(JvmInfo jvmInfo);
1523

0 commit comments

Comments
 (0)