diff --git a/communication/src/main/java/datadog/communication/BackendApiFactory.java b/communication/src/main/java/datadog/communication/BackendApiFactory.java index f3382792baa..4207d328044 100644 --- a/communication/src/main/java/datadog/communication/BackendApiFactory.java +++ b/communication/src/main/java/datadog/communication/BackendApiFactory.java @@ -77,7 +77,12 @@ public enum Intake { "http-intake.logs", "v2", Config::isAgentlessLogSubmissionEnabled, - Config::getAgentlessLogSubmissionUrl); + Config::getAgentlessLogSubmissionUrl), + CI_INTAKE( + "ci-intake", + "v2", + Config::isCiVisibilityAgentlessEnabled, + Config::getCiVisibilityIntakeAgentlessUrl); public final String urlPrefix; public final String version; diff --git a/dd-java-agent/agent-ci-visibility/build.gradle b/dd-java-agent/agent-ci-visibility/build.gradle index 6fea0f3ae44..1f0b8ddd0a4 100644 --- a/dd-java-agent/agent-ci-visibility/build.gradle +++ b/dd-java-agent/agent-ci-visibility/build.gradle @@ -64,6 +64,7 @@ dependencies { testFixturesApi group: 'com.jayway.jsonpath', name: 'json-path', version: '2.8.0' testFixturesApi group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.0' testFixturesApi group: 'org.msgpack', name: 'jackson-dataformat-msgpack', version: '0.9.6' + testFixturesApi group: 'org.xmlunit', name: 'xmlunit-core', version: '2.10.3' testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.9.2") // Required to update dependency lock files } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilityCoverageServices.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilityCoverageServices.java index 053bc90bd94..c83beb14283 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilityCoverageServices.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilityCoverageServices.java @@ -6,33 +6,45 @@ import datadog.trace.api.civisibility.coverage.CoverageStore; import datadog.trace.api.civisibility.coverage.NoOpCoverageStore; import datadog.trace.civisibility.config.ExecutionSettings; +import datadog.trace.civisibility.config.JvmInfo; import datadog.trace.civisibility.coverage.SkippableAwareCoverageStoreFactory; import datadog.trace.civisibility.coverage.file.FileCoverageStore; import datadog.trace.civisibility.coverage.line.LineCoverageStore; -import datadog.trace.civisibility.coverage.percentage.CoverageCalculator; -import datadog.trace.civisibility.coverage.percentage.JacocoCoverageCalculator; -import datadog.trace.civisibility.coverage.percentage.child.ChildProcessCoverageReporter; -import datadog.trace.civisibility.coverage.percentage.child.JacocoChildProcessCoverageReporter; +import datadog.trace.civisibility.coverage.report.CoverageProcessor; +import datadog.trace.civisibility.coverage.report.CoverageReportUploader; +import datadog.trace.civisibility.coverage.report.JacocoCoverageProcessor; +import datadog.trace.civisibility.coverage.report.child.ChildProcessCoverageReporter; +import datadog.trace.civisibility.coverage.report.child.JacocoChildProcessCoverageReporter; import datadog.trace.civisibility.domain.buildsystem.ModuleSignalRouter; import java.util.Map; /** - * Services that are related to coverage calculation (both per-test coverage and total coverage - * percentage). The scope is session/module. + * Services that are related to per-test code coverage and coverage report uploads. The scope is + * session/module. */ public class CiVisibilityCoverageServices { /** Services used in the parent process (build system). */ static class Parent { final ModuleSignalRouter moduleSignalRouter; - final CoverageCalculator.Factory coverageCalculatorFactory; + final CoverageProcessor.Factory coverageProcessorFactory; Parent(CiVisibilityServices services, CiVisibilityRepoServices repoServices) { moduleSignalRouter = new ModuleSignalRouter(); - coverageCalculatorFactory = - new JacocoCoverageCalculator.Factory( + + ExecutionSettings executionSettings = + repoServices.executionSettingsFactory.create(JvmInfo.CURRENT_JVM, null); + CoverageReportUploader coverageReportUploader = + executionSettings.isCodeCoverageReportUploadEnabled() + ? new CoverageReportUploader( + services.ciIntake, repoServices.ciTags, services.metricCollector) + : null; + + coverageProcessorFactory = + new JacocoCoverageProcessor.Factory( services.config, repoServices.repoIndexProvider, + coverageReportUploader, repoServices.repoRoot, moduleSignalRouter); } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilityServices.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilityServices.java index 2cd94ba8799..095614e46ad 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilityServices.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilityServices.java @@ -66,6 +66,7 @@ public class CiVisibilityServices { final Config config; final CiVisibilityMetricCollector metricCollector; final BackendApi backendApi; + final BackendApi ciIntake; final JvmInfoFactory jvmInfoFactory; final CiEnvironment environment; final CIProviderInfoFactory ciProviderInfoFactory; @@ -85,6 +86,8 @@ public class CiVisibilityServices { this.metricCollector = metricCollector; this.backendApi = new BackendApiFactory(config, sco).createBackendApi(BackendApiFactory.Intake.API); + this.ciIntake = + new BackendApiFactory(config, sco).createBackendApi(BackendApiFactory.Intake.CI_INTAKE); this.jvmInfoFactory = new CachingJvmInfoFactory(config, new JvmInfoFactoryImpl()); this.gitClientFactory = buildGitClientFactory(config, metricCollector); diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java index 575e9a38442..2d68404b83f 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/CiVisibilitySystem.java @@ -41,6 +41,7 @@ import java.lang.instrument.Instrumentation; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; @@ -241,7 +242,7 @@ private static BuildSystemSession.Factory buildSystemSessionFactory( repoServices.executionSettingsFactory, signalServer, repoServices.repoIndexProvider, - coverageServices.coverageCalculatorFactory); + coverageServices.coverageProcessorFactory); }; } @@ -266,6 +267,11 @@ private static TestFrameworkSession.Factory childTestFrameworkSessionFactory( repoServices.sourcePathResolver, services.linesResolver); + // only add report upload capability for children sessions, + // because report upload is only supported when the build system is instrumented + capabilities = new ArrayList<>(capabilities); + capabilities.add(LibraryCapability.COV_REPORT_UPLOAD); + return new ProxyTestSession( services.processHierarchy.parentProcessModuleContext, services.config, diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CiVisibilitySettings.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CiVisibilitySettings.java index bdce3fb02b8..ca5d6324b1a 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CiVisibilitySettings.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/CiVisibilitySettings.java @@ -17,6 +17,7 @@ public class CiVisibilitySettings { false, false, false, + false, EarlyFlakeDetectionSettings.DEFAULT, TestManagementSettings.DEFAULT, null); @@ -28,6 +29,7 @@ public class CiVisibilitySettings { private final boolean flakyTestRetriesEnabled; private final boolean impactedTestsDetectionEnabled; private final boolean knownTestsEnabled; + private final boolean coverageReportUploadEnabled; private final EarlyFlakeDetectionSettings earlyFlakeDetectionSettings; private final TestManagementSettings testManagementSettings; @Nullable private final String defaultBranch; @@ -40,6 +42,7 @@ public class CiVisibilitySettings { boolean flakyTestRetriesEnabled, boolean impactedTestsDetectionEnabled, boolean knownTestsEnabled, + boolean coverageReportUploadEnabled, EarlyFlakeDetectionSettings earlyFlakeDetectionSettings, TestManagementSettings testManagementSettings, @Nullable String defaultBranch) { @@ -50,6 +53,7 @@ public class CiVisibilitySettings { this.flakyTestRetriesEnabled = flakyTestRetriesEnabled; this.impactedTestsDetectionEnabled = impactedTestsDetectionEnabled; this.knownTestsEnabled = knownTestsEnabled; + this.coverageReportUploadEnabled = coverageReportUploadEnabled; this.earlyFlakeDetectionSettings = earlyFlakeDetectionSettings; this.testManagementSettings = testManagementSettings; this.defaultBranch = defaultBranch; @@ -83,6 +87,10 @@ public boolean isKnownTestsEnabled() { return knownTestsEnabled; } + public boolean isCoverageReportUploadEnabled() { + return coverageReportUploadEnabled; + } + public EarlyFlakeDetectionSettings getEarlyFlakeDetectionSettings() { return earlyFlakeDetectionSettings; } @@ -112,6 +120,7 @@ public boolean equals(Object o) { && flakyTestRetriesEnabled == that.flakyTestRetriesEnabled && impactedTestsDetectionEnabled == that.impactedTestsDetectionEnabled && knownTestsEnabled == that.knownTestsEnabled + && coverageReportUploadEnabled == that.coverageReportUploadEnabled && Objects.equals(earlyFlakeDetectionSettings, that.earlyFlakeDetectionSettings) && Objects.equals(testManagementSettings, that.testManagementSettings) && Objects.equals(defaultBranch, that.defaultBranch); @@ -127,6 +136,7 @@ public int hashCode() { flakyTestRetriesEnabled, impactedTestsDetectionEnabled, knownTestsEnabled, + coverageReportUploadEnabled, earlyFlakeDetectionSettings, testManagementSettings, defaultBranch); @@ -154,6 +164,7 @@ public CiVisibilitySettings fromJson(Map json) { getBoolean(json, "flaky_test_retries_enabled", false), getBoolean(json, "impacted_tests_enabled", false), getBoolean(json, "known_tests_enabled", false), + getBoolean(json, "coverage_report_upload_enabled", false), EarlyFlakeDetectionSettings.JsonAdapter.INSTANCE.fromJson( (Map) json.get("early_flake_detection")), TestManagementSettings.JsonAdapter.INSTANCE.fromJson( diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ExecutionSettings.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ExecutionSettings.java index 4d93dcf5d38..7c1809f534d 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ExecutionSettings.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ExecutionSettings.java @@ -26,6 +26,7 @@ public class ExecutionSettings { false, false, false, + false, EarlyFlakeDetectionSettings.DEFAULT, TestManagementSettings.DEFAULT, null, @@ -43,6 +44,7 @@ public class ExecutionSettings { private final boolean testSkippingEnabled; private final boolean flakyTestRetriesEnabled; private final boolean impactedTestsDetectionEnabled; + private final boolean codeCoverageReportUploadEnabled; @Nonnull private final EarlyFlakeDetectionSettings earlyFlakeDetectionSettings; @Nonnull private final TestManagementSettings testManagementSettings; @Nullable private final String itrCorrelationId; @@ -58,6 +60,7 @@ public ExecutionSettings( boolean testSkippingEnabled, boolean flakyTestRetriesEnabled, boolean impactedTestsDetectionEnabled, + boolean codeCoverageReportUploadEnabled, @Nonnull EarlyFlakeDetectionSettings earlyFlakeDetectionSettings, @Nonnull TestManagementSettings testManagementSettings, @Nullable String itrCorrelationId, @@ -74,6 +77,7 @@ public ExecutionSettings( this.testSkippingEnabled = testSkippingEnabled; this.flakyTestRetriesEnabled = flakyTestRetriesEnabled; this.impactedTestsDetectionEnabled = impactedTestsDetectionEnabled; + this.codeCoverageReportUploadEnabled = codeCoverageReportUploadEnabled; this.earlyFlakeDetectionSettings = earlyFlakeDetectionSettings; this.testManagementSettings = testManagementSettings; this.itrCorrelationId = itrCorrelationId; @@ -110,6 +114,7 @@ private ExecutionSettings( boolean testSkippingEnabled, boolean flakyTestRetriesEnabled, boolean impactedTestsDetectionEnabled, + boolean codeCoverageReportUploadEnabled, @Nonnull EarlyFlakeDetectionSettings earlyFlakeDetectionSettings, @Nonnull TestManagementSettings testManagementSettings, @Nullable String itrCorrelationId, @@ -123,6 +128,7 @@ private ExecutionSettings( this.testSkippingEnabled = testSkippingEnabled; this.flakyTestRetriesEnabled = flakyTestRetriesEnabled; this.impactedTestsDetectionEnabled = impactedTestsDetectionEnabled; + this.codeCoverageReportUploadEnabled = codeCoverageReportUploadEnabled; this.earlyFlakeDetectionSettings = earlyFlakeDetectionSettings; this.testManagementSettings = testManagementSettings; this.itrCorrelationId = itrCorrelationId; @@ -157,6 +163,10 @@ public boolean isImpactedTestsDetectionEnabled() { return impactedTestsDetectionEnabled; } + public boolean isCodeCoverageReportUploadEnabled() { + return codeCoverageReportUploadEnabled; + } + @Nonnull public EarlyFlakeDetectionSettings getEarlyFlakeDetectionSettings() { return earlyFlakeDetectionSettings; @@ -243,6 +253,7 @@ public boolean equals(Object o) { && testSkippingEnabled == that.testSkippingEnabled && flakyTestRetriesEnabled == that.flakyTestRetriesEnabled && impactedTestsDetectionEnabled == that.impactedTestsDetectionEnabled + && codeCoverageReportUploadEnabled == that.codeCoverageReportUploadEnabled && Objects.equals(earlyFlakeDetectionSettings, that.earlyFlakeDetectionSettings) && Objects.equals(testManagementSettings, that.testManagementSettings) && Objects.equals(itrCorrelationId, that.itrCorrelationId) @@ -261,6 +272,7 @@ public int hashCode() { testSkippingEnabled, flakyTestRetriesEnabled, impactedTestsDetectionEnabled, + codeCoverageReportUploadEnabled, earlyFlakeDetectionSettings, testManagementSettings, itrCorrelationId, @@ -278,6 +290,7 @@ public static class Serializer { private static final int TEST_SKIPPING_ENABLED_FLAG = 4; private static final int FLAKY_TEST_RETRIES_ENABLED_FLAG = 8; private static final int IMPACTED_TESTS_DETECTION_ENABLED_FLAG = 16; + private static final int CODE_COVERAGE_REPORT_UPLOAD_ENABLED_FLAG = 32; public static ByteBuffer serialize(ExecutionSettings settings) { datadog.trace.civisibility.ipc.serialization.Serializer s = @@ -291,6 +304,9 @@ public static ByteBuffer serialize(ExecutionSettings settings) { | (settings.flakyTestRetriesEnabled ? FLAKY_TEST_RETRIES_ENABLED_FLAG : 0) | (settings.impactedTestsDetectionEnabled ? IMPACTED_TESTS_DETECTION_ENABLED_FLAG + : 0) + | (settings.codeCoverageReportUploadEnabled + ? CODE_COVERAGE_REPORT_UPLOAD_ENABLED_FLAG : 0)); s.write(flags); @@ -330,6 +346,8 @@ public static ExecutionSettings deserialize(ByteBuffer buffer) { boolean testSkippingEnabled = (flags & TEST_SKIPPING_ENABLED_FLAG) != 0; boolean flakyTestRetriesEnabled = (flags & FLAKY_TEST_RETRIES_ENABLED_FLAG) != 0; boolean impactedTestsDetectionEnabled = (flags & IMPACTED_TESTS_DETECTION_ENABLED_FLAG) != 0; + boolean codeCoverageReportUploadEnabled = + (flags & CODE_COVERAGE_REPORT_UPLOAD_ENABLED_FLAG) != 0; EarlyFlakeDetectionSettings earlyFlakeDetectionSettings = EarlyFlakeDetectionSettings.Serializer.deserialize(buffer); @@ -372,6 +390,7 @@ public static ExecutionSettings deserialize(ByteBuffer buffer) { testSkippingEnabled, flakyTestRetriesEnabled, impactedTestsDetectionEnabled, + codeCoverageReportUploadEnabled, earlyFlakeDetectionSettings, testManagementSettings, itrCorrelationId, diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ExecutionSettingsFactoryImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ExecutionSettingsFactoryImpl.java index c0acb7f83a6..ca153fc9ce8 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ExecutionSettingsFactoryImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ExecutionSettingsFactoryImpl.java @@ -177,6 +177,11 @@ private Map doCreate( settings, CiVisibilitySettings::isKnownTestsEnabled, Config::isCiVisibilityKnownTestsRequestEnabled); + boolean codeCoverageReportUpload = + isFeatureEnabled( + settings, + CiVisibilitySettings::isCoverageReportUploadEnabled, + Config::isCiVisibilityCodeCoverageReportUploadEnabled); TestManagementSettings testManagementSettings = getTestManagementSettings(settings); @@ -189,7 +194,8 @@ private Map doCreate( + "Impacted tests detection - {},\n" + "Known tests marking - {},\n" + "Auto test retries - {},\n" - + "Test Management - {}", + + "Test Management - {},\n" + + "Code coverage report upload - {}", repositoryRoot, tracerEnvironment.getConfigurations().getRuntimeName(), tracerEnvironment.getConfigurations().getRuntimeVersion(), @@ -201,7 +207,8 @@ private Map doCreate( impactedTestsEnabled, knownTestsRequest, flakyTestRetriesEnabled, - testManagementSettings.isEnabled()); + testManagementSettings.isEnabled(), + codeCoverageReportUpload); Future skippableTestsFuture = executor.submit(() -> getSkippableTests(tracerEnvironment, itrEnabled)); @@ -253,6 +260,7 @@ private Map doCreate( testSkippingEnabled, flakyTestRetriesEnabled, impactedTestsEnabled, + codeCoverageReportUpload, earlyFlakeDetectionEnabled ? settings.getEarlyFlakeDetectionSettings() : EarlyFlakeDetectionSettings.DEFAULT, diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/NoOpCoverageCalculator.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/NoOpCoverageCalculator.java deleted file mode 100644 index b638a9eef00..00000000000 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/NoOpCoverageCalculator.java +++ /dev/null @@ -1,34 +0,0 @@ -package datadog.trace.civisibility.coverage.percentage; - -import datadog.trace.api.civisibility.domain.BuildModuleLayout; -import datadog.trace.civisibility.config.ExecutionSettings; -import org.jetbrains.annotations.Nullable; - -public class NoOpCoverageCalculator implements CoverageCalculator { - - private static final NoOpCoverageCalculator INSTANCE = new NoOpCoverageCalculator(); - - private NoOpCoverageCalculator() {} - - @Nullable - @Override - public Long calculateCoveragePercentage() { - return null; - } - - public static final class Factory implements CoverageCalculator.Factory { - @Override - public NoOpCoverageCalculator sessionCoverage(long sessionId) { - return INSTANCE; - } - - @Override - public NoOpCoverageCalculator moduleCoverage( - long moduleId, - BuildModuleLayout moduleLayout, - ExecutionSettings executionSettings, - NoOpCoverageCalculator sessionCoverage) { - return INSTANCE; - } - } -} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/CoverageCalculator.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/CoverageProcessor.java similarity index 56% rename from dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/CoverageCalculator.java rename to dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/CoverageProcessor.java index c7fcaa3668d..d3cc3ecb5d0 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/CoverageCalculator.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/CoverageProcessor.java @@ -1,15 +1,16 @@ -package datadog.trace.civisibility.coverage.percentage; +package datadog.trace.civisibility.coverage.report; import datadog.trace.api.civisibility.domain.BuildModuleLayout; import datadog.trace.civisibility.config.ExecutionSettings; import javax.annotation.Nullable; -/** Calculates percentage of executable lines that are covered with tests. */ -public interface CoverageCalculator { +/** Processes coverage reports. */ +public interface CoverageProcessor { + /** Processes previously collected coverage data and returns the percentage of lines covered. */ @Nullable - Long calculateCoveragePercentage(); + Long processCoverageData(); - interface Factory { + interface Factory { T sessionCoverage(long sessionId); T moduleCoverage( diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/CoverageReportUploader.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/CoverageReportUploader.java new file mode 100644 index 00000000000..65423dd46ae --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/CoverageReportUploader.java @@ -0,0 +1,104 @@ +package datadog.trace.civisibility.coverage.report; + +import static datadog.communication.http.OkHttpUtils.jsonRequestBodyOf; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; +import com.squareup.moshi.Types; +import datadog.communication.BackendApi; +import datadog.communication.http.OkHttpUtils; +import datadog.trace.api.civisibility.telemetry.CiVisibilityCountMetric; +import datadog.trace.api.civisibility.telemetry.CiVisibilityDistributionMetric; +import datadog.trace.api.civisibility.telemetry.CiVisibilityMetricCollector; +import datadog.trace.civisibility.communication.TelemetryListener; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.GZIPOutputStream; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import okio.BufferedSink; + +public class CoverageReportUploader { + + private final BackendApi backendApi; + private final Map ciTags; + private final CiVisibilityMetricCollector metricCollector; + private final JsonAdapter> eventAdapter; + + public CoverageReportUploader( + BackendApi backendApi, + Map ciTags, + CiVisibilityMetricCollector metricCollector) { + this.backendApi = backendApi; + this.ciTags = ciTags; + this.metricCollector = metricCollector; + + Moshi moshi = new Moshi.Builder().build(); + Type type = Types.newParameterizedType(Map.class, String.class, String.class); + eventAdapter = moshi.adapter(type); + } + + public void upload(String format, InputStream reportStream) throws IOException { + Map event = new HashMap<>(ciTags); + event.put("format", format); + event.put("type", "coverage_report"); + String eventJson = eventAdapter.toJson(event); + RequestBody eventBody = jsonRequestBodyOf(eventJson.getBytes(StandardCharsets.UTF_8)); + + RequestBody coverageBody = new GzipMultipartRequestBody(reportStream); + + MultipartBody multipartBody = + new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("coverage", "coverage.gz", coverageBody) + .addFormDataPart("event", "event.json", eventBody) + .build(); + + OkHttpUtils.CustomListener telemetryListener = + new TelemetryListener.Builder(metricCollector) + .requestCount(CiVisibilityCountMetric.COVERAGE_UPLOAD_REQUEST) + .requestBytes(CiVisibilityDistributionMetric.COVERAGE_UPLOAD_REQUEST_BYTES) + .requestErrors(CiVisibilityCountMetric.COVERAGE_UPLOAD_REQUEST_ERRORS) + .requestDuration(CiVisibilityDistributionMetric.KNOWN_TESTS_REQUEST_MS) + .build(); + + backendApi.post("cicovreprt", multipartBody, responseStream -> null, telemetryListener, false); + } + + /** Request body that compresses a form data part */ + private static class GzipMultipartRequestBody extends RequestBody { + private final InputStream stream; + + private GzipMultipartRequestBody(InputStream stream) { + this.stream = stream; + } + + @Override + public long contentLength() { + return -1; + } + + @Override + public MediaType contentType() { + return null; + } + + @SuppressFBWarnings("OS_OPEN_STREAM") + @Override + public void writeTo(BufferedSink sink) throws IOException { + GZIPOutputStream outputStream = new GZIPOutputStream(sink.outputStream()); + byte[] buffer = new byte[8192]; + for (int readCount; (readCount = stream.read(buffer)) != -1; ) { + outputStream.write(buffer, 0, readCount); + } + outputStream.finish(); + // not closing output stream as it would close the underlying sink, which is managed by okhttp + } + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/JacocoCoverageCalculator.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/JacocoCoverageProcessor.java similarity index 77% rename from dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/JacocoCoverageCalculator.java rename to dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/JacocoCoverageProcessor.java index c275d18a7eb..7e31f92aea0 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/JacocoCoverageCalculator.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/JacocoCoverageProcessor.java @@ -1,4 +1,4 @@ -package datadog.trace.civisibility.coverage.percentage; +package datadog.trace.civisibility.coverage.report; import datadog.trace.api.Config; import datadog.trace.api.civisibility.domain.BuildModuleLayout; @@ -15,11 +15,13 @@ import datadog.trace.util.Strings; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.BitSet; @@ -28,6 +30,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.TreeMap; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; @@ -35,6 +38,7 @@ import org.jacoco.core.analysis.CoverageBuilder; import org.jacoco.core.analysis.IBundleCoverage; import org.jacoco.core.analysis.ICounter; +import org.jacoco.core.analysis.ILine; import org.jacoco.core.analysis.IPackageCoverage; import org.jacoco.core.analysis.ISourceFileCoverage; import org.jacoco.core.analysis.ISourceNode; @@ -50,41 +54,45 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** Uses Jacoco execution data to calculate the percentage of covered lines. */ -public class JacocoCoverageCalculator implements CoverageCalculator { +/** Processes Jacoco coverage reports. */ +public class JacocoCoverageProcessor implements CoverageProcessor { - public static final class Factory - implements CoverageCalculator.Factory { + public static final class Factory implements CoverageProcessor.Factory { private final Config config; private final RepoIndexProvider repoIndexProvider; + private final CoverageReportUploader coverageReportUploader; @Nullable private final String repoRoot; private final ModuleSignalRouter moduleSignalRouter; public Factory( Config config, RepoIndexProvider repoIndexProvider, + CoverageReportUploader coverageReportUploader, @Nullable String repoRoot, ModuleSignalRouter moduleSignalRouter) { this.config = config; this.repoIndexProvider = repoIndexProvider; + this.coverageReportUploader = coverageReportUploader; this.repoRoot = repoRoot; this.moduleSignalRouter = moduleSignalRouter; } @Override - public JacocoCoverageCalculator sessionCoverage(long sessionId) { - return new JacocoCoverageCalculator(config, repoIndexProvider, repoRoot, sessionId); + public JacocoCoverageProcessor sessionCoverage(long sessionId) { + return new JacocoCoverageProcessor( + config, repoIndexProvider, coverageReportUploader, repoRoot, sessionId); } @Override - public JacocoCoverageCalculator moduleCoverage( + public JacocoCoverageProcessor moduleCoverage( long moduleId, @Nullable BuildModuleLayout moduleLayout, ExecutionSettings executionSettings, - JacocoCoverageCalculator sessionCoverage) { - return new JacocoCoverageCalculator( + JacocoCoverageProcessor sessionCoverage) { + return new JacocoCoverageProcessor( config, repoIndexProvider, + null, // do not upload coverage reports for individual modules executionSettings, repoRoot, moduleId, @@ -94,14 +102,16 @@ public JacocoCoverageCalculator moduleCoverage( } } - private static final Logger LOGGER = LoggerFactory.getLogger(JacocoCoverageCalculator.class); + private static final Logger LOGGER = LoggerFactory.getLogger(JacocoCoverageProcessor.class); - @Nullable private final JacocoCoverageCalculator parent; + @Nullable private final JacocoCoverageProcessor parent; private final Config config; private final RepoIndexProvider repoIndexProvider; + @Nullable private final CoverageReportUploader coverageReportUploader; + @Nullable private final String repoRoot; private final long eventId; @@ -117,30 +127,34 @@ public JacocoCoverageCalculator moduleCoverage( @GuardedBy("coverageDataLock") private final Collection outputClassesDirs = new HashSet<>(); - private JacocoCoverageCalculator( + private JacocoCoverageProcessor( Config config, RepoIndexProvider repoIndexProvider, + @Nullable CoverageReportUploader coverageReportUploader, @Nullable String repoRoot, long sessionId) { this.parent = null; this.config = config; this.repoIndexProvider = repoIndexProvider; + this.coverageReportUploader = coverageReportUploader; this.repoRoot = repoRoot; this.eventId = sessionId; } - private JacocoCoverageCalculator( + private JacocoCoverageProcessor( Config config, RepoIndexProvider repoIndexProvider, + @Nullable CoverageReportUploader coverageReportUploader, ExecutionSettings executionSettings, @Nullable String repoRoot, long moduleId, @Nullable BuildModuleLayout moduleLayout, ModuleSignalRouter moduleSignalRouter, - @Nonnull JacocoCoverageCalculator parent) { + @Nonnull JacocoCoverageProcessor parent) { this.parent = parent; this.config = config; this.repoIndexProvider = repoIndexProvider; + this.coverageReportUploader = coverageReportUploader; this.repoRoot = repoRoot; this.eventId = moduleId; @@ -177,7 +191,7 @@ private void addModuleLayout(@Nullable BuildModuleLayout moduleLayout) { private void addBackendCoverageData(@Nonnull Map skippableTestsCoverage) { synchronized (coverageDataLock) { for (Map.Entry e : skippableTestsCoverage.entrySet()) { - backendCoverageData.merge(e.getKey(), e.getValue(), JacocoCoverageCalculator::mergeBitSets); + backendCoverageData.merge(e.getKey(), e.getValue(), JacocoCoverageProcessor::mergeBitSets); } } if (parent != null) { @@ -240,7 +254,7 @@ private static ExecutionDataStore parseCoverageData(byte[] rawCoverageData) { @Override @Nullable - public Long calculateCoveragePercentage() { + public Long processCoverageData() { IBundleCoverage coverageBundle = buildCoverageBundle(); if (coverageBundle == null) { return null; @@ -251,7 +265,12 @@ public Long calculateCoveragePercentage() { dumpCoverageReport(coverageBundle, coverageReportFolder); } - return getCoveragePercentage(coverageBundle); + if (backendCoverageData.isEmpty()) { + uploadCoverageReport(coverageBundle); + return getLocalCoveragePercentage(coverageBundle); + } else { + return mergeAndUploadCoverageReport(coverageBundle); + } } private IBundleCoverage buildCoverageBundle() { @@ -368,11 +387,22 @@ public int getTabWidth() { } } - private long getCoveragePercentage(IBundleCoverage coverageBundle) { - if (backendCoverageData.isEmpty()) { - return getLocalCoveragePercentage(coverageBundle); - } else { - return getMergedCoveragePercentage(coverageBundle); + private void uploadCoverageReport(IBundleCoverage coverageBundle) { + if (coverageReportUploader == null) { + return; + } + + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + XMLFormatter xmlFormatter = new XMLFormatter(); + IReportVisitor xmlVisitor = xmlFormatter.createVisitor(baos); + xmlVisitor.visitInfo(Collections.emptyList(), Collections.emptyList()); + xmlVisitor.visitBundle(coverageBundle, createSourceFileLocator()); + xmlVisitor.visitEnd(); + + coverageReportUploader.upload("jacoco", new ByteArrayInputStream(baos.toByteArray())); + + } catch (IOException e) { + LOGGER.error("Error while uploading coverage report", e); } } @@ -389,10 +419,17 @@ private static long getLocalCoveragePercentage(IBundleCoverage coverageBundle) { private static final BitSet EMPTY_BIT_SET = new BitSet(); - /** Merges coverage from Jacoco bundle with skipped tests' coverage received from the backend. */ - private long getMergedCoveragePercentage(IBundleCoverage coverageBundle) { + /** + * Merges coverage from Jacoco bundle with skipped tests' coverage received from the backend and + * uploads the merge result if applicable. + * + * @return total coverage percentage + */ + private long mergeAndUploadCoverageReport(IBundleCoverage coverageBundle) { RepoIndex repoIndex = repoIndexProvider.getIndex(); + Map mergedCoverageData = new TreeMap<>(); + int totalLines = 0, coveredLines = 0; for (IPackageCoverage packageCoverage : coverageBundle.getPackages()) { for (ISourceFileCoverage sourceFile : packageCoverage.getSourceFiles()) { @@ -408,35 +445,58 @@ private long getMergedCoveragePercentage(IBundleCoverage coverageBundle) { continue; } - BitSet sourceFileCoveredLines = getCoveredLines(sourceFile); + LinesCoverage linesCoverage = getLinesCoverage(sourceFile); // backendCoverageData contains data for all modules in the repo, // but coverageBundle bundle only has source files that are relevant for the given module, // so we are not taking into account any backend coverage that is not relevant - sourceFileCoveredLines.or( + linesCoverage.coveredLines.or( backendCoverageData.getOrDefault(pathRelativeToIndexRoot, EMPTY_BIT_SET)); - coveredLines += sourceFileCoveredLines.cardinality(); + mergedCoverageData.put(pathRelativeToIndexRoot, linesCoverage); + + coveredLines += linesCoverage.coveredLines.cardinality(); totalLines += sourceFile.getLineCounter().getTotalCount(); } } + + uploadMergedCoverageReport(mergedCoverageData); + return Math.round((100d * coveredLines) / totalLines); } - private static BitSet getCoveredLines(ISourceNode coverage) { - BitSet bitSet = new BitSet(); + private void uploadMergedCoverageReport(Map mergedCoverageData) { + if (coverageReportUploader == null) { + return; + } + + String lcovReport = LcovReportWriter.toString(mergedCoverageData); + try { + coverageReportUploader.upload( + "lcov", new ByteArrayInputStream(lcovReport.getBytes(StandardCharsets.UTF_8))); + } catch (IOException e) { + LOGGER.error("Error while uploading coverage report", e); + } + } + + private static LinesCoverage getLinesCoverage(ISourceNode coverage) { + LinesCoverage linesCoverage = new LinesCoverage(); int firstLine = coverage.getFirstLine(); if (firstLine == -1) { - return bitSet; + return linesCoverage; } int lastLine = coverage.getLastLine(); - for (int line = firstLine; line <= lastLine; line++) { - if (coverage.getLine(line).getStatus() >= ICounter.FULLY_COVERED) { - bitSet.set(line); + for (int lineIdx = firstLine; lineIdx <= lastLine; lineIdx++) { + ILine line = coverage.getLine(lineIdx); + if (line.getStatus() > ICounter.EMPTY) { + linesCoverage.executableLines.set(lineIdx); + if (line.getStatus() >= ICounter.FULLY_COVERED) { + linesCoverage.coveredLines.set(lineIdx); + } } } - return bitSet; + return linesCoverage; } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/LcovReportWriter.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/LcovReportWriter.java new file mode 100644 index 00000000000..b7fc48c8873 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/LcovReportWriter.java @@ -0,0 +1,73 @@ +package datadog.trace.civisibility.coverage.report; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import javax.annotation.Nonnull; + +public final class LcovReportWriter { + private LcovReportWriter() {} + + public static void write(Map coverage, Writer out) throws IOException { + Objects.requireNonNull(coverage, "coverage"); + Objects.requireNonNull(out, "out"); + + Map sorted = + (coverage instanceof TreeMap) ? coverage : toTreeMap(coverage); + + for (Map.Entry e : sorted.entrySet()) { + String path = e.getKey(); + LinesCoverage lc = e.getValue(); + if (path == null || path.isEmpty()) { + continue; + } + if (lc == null) { + lc = new LinesCoverage(); + } + + out.write("SF:" + path + "\n"); + + int lf = 0; // lines found (instrumented) + int lh = 0; // lines hit (executed at least once) + + for (int line = lc.executableLines.nextSetBit(1); + line >= 0; + line = lc.executableLines.nextSetBit(line + 1)) { // skip bit 0 + lf++; + int count = lc.coveredLines.get(line) ? 1 : 0; + lh += count; + out.write("DA:" + line + "," + count + "\n"); + } + + out.write("LH:" + lh + "\n"); + out.write("LF:" + lf + "\n"); + out.write("end_of_record\n"); + } + } + + @Nonnull + private static TreeMap toTreeMap(Map coverage) { + TreeMap treeMap = new TreeMap<>(); + for (Map.Entry e : coverage.entrySet()) { + if (e.getKey() == null) { + continue; + } + treeMap.put(e.getKey(), e.getValue()); + } + return treeMap; + } + + public static String toString(Map coverage) { + try { + StringWriter sw = new StringWriter(); + write(coverage, sw); + return sw.toString(); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/LinesCoverage.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/LinesCoverage.java new file mode 100644 index 00000000000..962467439a1 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/LinesCoverage.java @@ -0,0 +1,8 @@ +package datadog.trace.civisibility.coverage.report; + +import java.util.BitSet; + +public final class LinesCoverage { + public final BitSet coveredLines = new BitSet(); + public final BitSet executableLines = new BitSet(); +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/NoOpCoverageProcessor.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/NoOpCoverageProcessor.java new file mode 100644 index 00000000000..90929e33784 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/NoOpCoverageProcessor.java @@ -0,0 +1,34 @@ +package datadog.trace.civisibility.coverage.report; + +import datadog.trace.api.civisibility.domain.BuildModuleLayout; +import datadog.trace.civisibility.config.ExecutionSettings; +import org.jetbrains.annotations.Nullable; + +public class NoOpCoverageProcessor implements CoverageProcessor { + + private static final NoOpCoverageProcessor INSTANCE = new NoOpCoverageProcessor(); + + private NoOpCoverageProcessor() {} + + @Nullable + @Override + public Long processCoverageData() { + return null; + } + + public static final class Factory implements CoverageProcessor.Factory { + @Override + public NoOpCoverageProcessor sessionCoverage(long sessionId) { + return INSTANCE; + } + + @Override + public NoOpCoverageProcessor moduleCoverage( + long moduleId, + BuildModuleLayout moduleLayout, + ExecutionSettings executionSettings, + NoOpCoverageProcessor sessionCoverage) { + return INSTANCE; + } + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/child/ChildProcessCoverageReporter.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/child/ChildProcessCoverageReporter.java similarity index 91% rename from dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/child/ChildProcessCoverageReporter.java rename to dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/child/ChildProcessCoverageReporter.java index dffc9b74520..f6b061813a2 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/child/ChildProcessCoverageReporter.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/child/ChildProcessCoverageReporter.java @@ -1,4 +1,4 @@ -package datadog.trace.civisibility.coverage.percentage.child; +package datadog.trace.civisibility.coverage.report.child; import datadog.trace.api.DDTraceId; import datadog.trace.civisibility.ipc.ModuleSignal; diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/child/JacocoChildProcessCoverageReporter.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/child/JacocoChildProcessCoverageReporter.java similarity index 93% rename from dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/child/JacocoChildProcessCoverageReporter.java rename to dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/child/JacocoChildProcessCoverageReporter.java index a27947597ee..aba4b7b6f57 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/percentage/child/JacocoChildProcessCoverageReporter.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/coverage/report/child/JacocoChildProcessCoverageReporter.java @@ -1,4 +1,4 @@ -package datadog.trace.civisibility.coverage.percentage.child; +package datadog.trace.civisibility.coverage.report.child; import datadog.trace.api.DDTraceId; import datadog.trace.civisibility.ipc.ModuleCoverageDataJacoco; diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/BuildSystemModuleImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/BuildSystemModuleImpl.java index 70b36f11b59..812bb045233 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/BuildSystemModuleImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/BuildSystemModuleImpl.java @@ -20,7 +20,7 @@ import datadog.trace.civisibility.Constants; import datadog.trace.civisibility.codeowners.Codeowners; import datadog.trace.civisibility.config.ExecutionSettings; -import datadog.trace.civisibility.coverage.percentage.CoverageCalculator; +import datadog.trace.civisibility.coverage.report.CoverageProcessor; import datadog.trace.civisibility.decorator.TestDecorator; import datadog.trace.civisibility.domain.AbstractTestModule; import datadog.trace.civisibility.domain.BuildSystemModule; @@ -45,7 +45,7 @@ public class BuildSystemModuleImpl extends AbstractTestModule implements BuildSystemModule { - private final CoverageCalculator coverageCalculator; + private final CoverageProcessor coverageProcessor; private final ModuleSignalRouter moduleSignalRouter; private final BuildModuleSettings settings; @@ -53,7 +53,7 @@ public class BuildSystemModuleImpl extends AbstractTestModule implements BuildSy private volatile boolean testSkippingEnabled; - public BuildSystemModuleImpl( + public BuildSystemModuleImpl( AgentSpanContext sessionSpanContext, String moduleName, String startCommand, @@ -69,7 +69,7 @@ public BuildSystemModuleImpl( Codeowners codeowners, LinesResolver linesResolver, ModuleSignalRouter moduleSignalRouter, - CoverageCalculator.Factory coverageCalculatorFactory, + CoverageProcessor.Factory coverageProcessorFactory, T sessionCoverageCalculator, ExecutionSettings executionSettings, BuildSessionSettings sessionSettings, @@ -86,8 +86,8 @@ public BuildSystemModuleImpl( codeowners, linesResolver, onSpanFinish); - this.coverageCalculator = - coverageCalculatorFactory.moduleCoverage( + this.coverageProcessor = + coverageProcessorFactory.moduleCoverage( span.getSpanId(), moduleLayout, executionSettings, sessionCoverageCalculator); this.moduleSignalRouter = moduleSignalRouter; @@ -305,7 +305,7 @@ public void end(@Nullable Long endTime) { } } - Long coveragePercentage = coverageCalculator.calculateCoveragePercentage(); + Long coveragePercentage = coverageProcessor.processCoverageData(); if (coveragePercentage != null) { setTag(Tags.TEST_CODE_COVERAGE_LINES_PERCENTAGE, coveragePercentage); diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/BuildSystemSessionImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/BuildSystemSessionImpl.java index 330d2aa8167..d87886c6c00 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/BuildSystemSessionImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/BuildSystemSessionImpl.java @@ -19,7 +19,7 @@ import datadog.trace.civisibility.config.ExecutionSettings; import datadog.trace.civisibility.config.ExecutionSettingsFactory; import datadog.trace.civisibility.config.JvmInfo; -import datadog.trace.civisibility.coverage.percentage.CoverageCalculator; +import datadog.trace.civisibility.coverage.report.CoverageProcessor; import datadog.trace.civisibility.decorator.TestDecorator; import datadog.trace.civisibility.domain.AbstractTestSession; import datadog.trace.civisibility.domain.BuildSystemSession; @@ -43,7 +43,7 @@ import java.util.List; import javax.annotation.Nullable; -public class BuildSystemSessionImpl extends AbstractTestSession +public class BuildSystemSessionImpl extends AbstractTestSession implements BuildSystemSession { private final String startCommand; @@ -51,8 +51,8 @@ public class BuildSystemSessionImpl extends Abstra private final ExecutionSettingsFactory executionSettingsFactory; private final SignalServer signalServer; private final RepoIndexProvider repoIndexProvider; - private final CoverageCalculator.Factory coverageCalculatorFactory; - private final T coverageCalculator; + private final CoverageProcessor.Factory coverageProcessorFactory; + private final T coverageProcessor; private final BuildSessionSettings settings; public BuildSystemSessionImpl( @@ -70,7 +70,7 @@ public BuildSystemSessionImpl( ExecutionSettingsFactory executionSettingsFactory, SignalServer signalServer, RepoIndexProvider repoIndexProvider, - CoverageCalculator.Factory coverageCalculatorFactory) { + CoverageProcessor.Factory coverageProcessorFactory) { super( projectName, startTime, @@ -87,10 +87,14 @@ public BuildSystemSessionImpl( this.executionSettingsFactory = executionSettingsFactory; this.signalServer = signalServer; this.repoIndexProvider = repoIndexProvider; - this.coverageCalculatorFactory = coverageCalculatorFactory; - this.coverageCalculator = coverageCalculatorFactory.sessionCoverage(span.getSpanId()); + this.coverageProcessorFactory = coverageProcessorFactory; + this.coverageProcessor = coverageProcessorFactory.sessionCoverage(span.getSpanId()); + + ExecutionSettings executionSettings = + executionSettingsFactory.create(JvmInfo.CURRENT_JVM, null); this.settings = new BuildSessionSettings( + executionSettings.isCodeCoverageReportUploadEnabled(), getCoverageIncludedPackages(config, repoIndexProvider), config.getCiVisibilityCodeCoverageExcludes()); @@ -168,8 +172,8 @@ public BuildSystemModuleImpl testModuleStart( codeowners, linesResolver, moduleSignalRouter, - coverageCalculatorFactory, - coverageCalculator, + coverageProcessorFactory, + coverageProcessor, executionSettings, settings, this::onModuleFinish); @@ -212,7 +216,7 @@ protected Collection additionalTelemetryTags() { public void end(@Nullable Long endTime) { signalServer.stop(); - Long coveragePercentage = coverageCalculator.calculateCoveragePercentage(); + Long coveragePercentage = coverageProcessor.processCoverageData(); if (coveragePercentage != null) { setTag(Tags.TEST_CODE_COVERAGE_LINES_PERCENTAGE, coveragePercentage); diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/ProxyTestModule.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/ProxyTestModule.java index 5603bab9c20..e628bbf7471 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/ProxyTestModule.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/ProxyTestModule.java @@ -17,7 +17,7 @@ import datadog.trace.civisibility.config.EarlyFlakeDetectionSettings; import datadog.trace.civisibility.config.ExecutionSettings; import datadog.trace.civisibility.config.TestManagementSettings; -import datadog.trace.civisibility.coverage.percentage.child.ChildProcessCoverageReporter; +import datadog.trace.civisibility.coverage.report.child.ChildProcessCoverageReporter; import datadog.trace.civisibility.decorator.TestDecorator; import datadog.trace.civisibility.domain.InstrumentationType; import datadog.trace.civisibility.domain.TestFrameworkModule; diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/ProxyTestSession.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/ProxyTestSession.java index aefb9f05d4b..69ea8126b44 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/ProxyTestSession.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/ProxyTestSession.java @@ -7,7 +7,7 @@ import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import datadog.trace.civisibility.codeowners.Codeowners; -import datadog.trace.civisibility.coverage.percentage.child.ChildProcessCoverageReporter; +import datadog.trace.civisibility.coverage.report.child.ChildProcessCoverageReporter; import datadog.trace.civisibility.decorator.TestDecorator; import datadog.trace.civisibility.domain.TestFrameworkModule; import datadog.trace.civisibility.domain.TestFrameworkSession; diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ConfigurationApiImplTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ConfigurationApiImplTest.groovy index c284fce0199..2b9b9af2f44 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ConfigurationApiImplTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ConfigurationApiImplTest.groovy @@ -55,10 +55,10 @@ class ConfigurationApiImplTest extends Specification { where: agentless | compression | expectedSettings - false | false | new CiVisibilitySettings(false, false, false, false, false, false, false, EarlyFlakeDetectionSettings.DEFAULT, TestManagementSettings.DEFAULT, null) - false | true | new CiVisibilitySettings(true, true, true, true, true, true, true, EarlyFlakeDetectionSettings.DEFAULT, TestManagementSettings.DEFAULT, "main") - true | false | new CiVisibilitySettings(false, true, false, true, false, true, false, new EarlyFlakeDetectionSettings(true, [new ExecutionsByDuration(1000, 3)], 10), new TestManagementSettings(true, 10), "master") - true | true | new CiVisibilitySettings(false, false, true, true, false, false, true, new EarlyFlakeDetectionSettings(true, [new ExecutionsByDuration(5000, 3), new ExecutionsByDuration(120000, 2)], 10), new TestManagementSettings(true, 20), "prod") + false | false | new CiVisibilitySettings(false, false, false, false, false, false, false, false, EarlyFlakeDetectionSettings.DEFAULT, TestManagementSettings.DEFAULT, null) + false | true | new CiVisibilitySettings(true, true, true, true, true, true, true, true, EarlyFlakeDetectionSettings.DEFAULT, TestManagementSettings.DEFAULT, "main") + true | false | new CiVisibilitySettings(false, true, false, true, false, true, false, false, new EarlyFlakeDetectionSettings(true, [new ExecutionsByDuration(1000, 3)], 10), new TestManagementSettings(true, 10), "master") + true | true | new CiVisibilitySettings(false, false, true, true, false, false, true, true, new EarlyFlakeDetectionSettings(true, [new ExecutionsByDuration(5000, 3), new ExecutionsByDuration(120000, 2)], 10), new TestManagementSettings(true, 20), "prod") } def "test skippable tests request"() { diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ExecutionSettingsTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ExecutionSettingsTest.groovy index ace81272587..ce99c31354d 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ExecutionSettingsTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/ExecutionSettingsTest.groovy @@ -26,6 +26,7 @@ class ExecutionSettingsTest extends DDSpecification { false, false, false, + false, EarlyFlakeDetectionSettings.DEFAULT, TestManagementSettings.DEFAULT, null, @@ -44,6 +45,7 @@ class ExecutionSettingsTest extends DDSpecification { false, true, true, + true, new EarlyFlakeDetectionSettings(true, [], 10), new TestManagementSettings(true, 20), "", @@ -63,6 +65,7 @@ class ExecutionSettingsTest extends DDSpecification { true, false, true, + false, new EarlyFlakeDetectionSettings(true, [new ExecutionsByDuration(10, 20)], 10), new TestManagementSettings(true, 20), "itrCorrelationId", @@ -86,6 +89,7 @@ class ExecutionSettingsTest extends DDSpecification { true, true, true, + true, new EarlyFlakeDetectionSettings(true, [new ExecutionsByDuration(10, 20), new ExecutionsByDuration(30, 40)], 10), new TestManagementSettings(true, 20), "itrCorrelationId", diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/coverage/report/CoverageReportUploaderTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/coverage/report/CoverageReportUploaderTest.groovy new file mode 100644 index 00000000000..e67fd34f8e2 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/coverage/report/CoverageReportUploaderTest.groovy @@ -0,0 +1,96 @@ +package datadog.trace.civisibility.coverage.report + +import com.fasterxml.jackson.databind.ObjectMapper +import datadog.communication.BackendApi +import datadog.communication.BackendApiFactory +import datadog.communication.IntakeApi +import datadog.communication.http.HttpRetryPolicy +import datadog.communication.http.OkHttpUtils +import datadog.trace.api.civisibility.telemetry.CiVisibilityMetricCollector +import datadog.trace.test.util.MultipartRequestParser +import okhttp3.HttpUrl +import okhttp3.OkHttpClient +import spock.lang.AutoCleanup +import spock.lang.Shared +import spock.lang.Specification + +import java.util.zip.GZIPInputStream + +import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer + +class CoverageReportUploaderTest extends Specification { + + private static final int REQUEST_TIMEOUT_MILLIS = 15_000 + + private static final ObjectMapper JSON_MAPPER = new ObjectMapper() + + private static final String COVERAGE_REPORT_BODY = "report-body" + private static final String JACOCO_FORMAT = "jacoco" + private static final String CI_TAG_KEY = "ci-tag-key" + private static final String CI_TAG_VALUE = "ci-tag-value" + + @AutoCleanup + @Shared + def server = httpServer { + handlers { + prefix('/api/v2/cicovreprt') { + def parsed = MultipartRequestParser.parseRequest(request.body, request.headers.get("Content-Type")) + + def event = JSON_MAPPER.readValue(parsed["event"].get(0).get(), Map) + if (event.size() != 3 || event["format"] != JACOCO_FORMAT || event["type"] != "coverage_report" || event[CI_TAG_KEY] != CI_TAG_VALUE) { + response.status(400).send("Incorrect event $event") + return + } + + def coverage = new String(gunzip(parsed["coverage"].get(0).get())) + if (coverage != COVERAGE_REPORT_BODY) { + response.status(400).send("Incorrect coverage $coverage") + return + } + + response.status(200).send() + } + } + } + + static byte[] gunzip(byte[] compressed) throws IOException { + try (ByteArrayInputStream bais = new ByteArrayInputStream(compressed) + GZIPInputStream gis = new GZIPInputStream(bais) + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + + byte[] buffer = new byte[8192] + int len + while ((len = gis.read(buffer)) != -1) { + baos.write(buffer, 0, len) + } + return baos.toByteArray() + } + } + + def "test upload coverage report"() { + setup: + def backendApi = givenIntakeApi() + def metricCollector = Stub(CiVisibilityMetricCollector) + def uploader = new CoverageReportUploader(backendApi, [(CI_TAG_KEY):CI_TAG_VALUE], metricCollector) + def report = new ByteArrayInputStream(COVERAGE_REPORT_BODY.getBytes()) + + expect: + uploader.upload(JACOCO_FORMAT, report) + } + + private BackendApi givenIntakeApi() { + HttpUrl intakeUrl = HttpUrl.get(String.format("%s/api/%s/", server.address.toString(), BackendApiFactory.Intake.CI_INTAKE.version)) + + String apiKey = "api-key" + String traceId = "a-trace-id" + + HttpRetryPolicy retryPolicy = Stub(HttpRetryPolicy) + retryPolicy.shouldRetry(_) >> false + + HttpRetryPolicy.Factory retryPolicyFactory = Stub(HttpRetryPolicy.Factory) + retryPolicyFactory.create() >> retryPolicy + + OkHttpClient client = OkHttpUtils.buildHttpClient(intakeUrl, REQUEST_TIMEOUT_MILLIS) + return new IntakeApi(intakeUrl, apiKey, traceId, retryPolicyFactory, client, false) + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/coverage/report/LcovReportWriterTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/coverage/report/LcovReportWriterTest.groovy new file mode 100644 index 00000000000..a1c2d3c0478 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/coverage/report/LcovReportWriterTest.groovy @@ -0,0 +1,132 @@ +package datadog.trace.civisibility.coverage.report + +import spock.lang.Specification + +class LcovReportWriterTest extends Specification { + + def "empty map produces empty output"() { + expect: + LcovReportWriter.toString(Collections.emptyMap()) == "" + } + + def "single file with executable and covered lines"() { + given: + def lc = new LinesCoverage() + lc.executableLines.set(1); lc.executableLines.set(2); lc.executableLines.set(3) + lc.coveredLines.set(1); lc.coveredLines.set(3) + def m = Collections.singletonMap("src/Foo.java", lc) + + expect: + LcovReportWriter.toString(m) == """SF:src/Foo.java +DA:1,1 +DA:2,0 +DA:3,1 +LH:2 +LF:3 +end_of_record +""" + } + + def "ignores bit 0 in both sets and handles very large lines"() { + given: + def lc = new LinesCoverage() + lc.executableLines.set(0); lc.executableLines.set(2); lc.executableLines.set(10000) + lc.coveredLines.set(0); lc.coveredLines.set(2); lc.coveredLines.set(10000) + def m = Collections.singletonMap("a.java", lc) + + when: + def out = LcovReportWriter.toString(m) + + then: + out.contains("SF:a.java\n") + out.contains("DA:2,1\n") + out.contains("DA:10000,1\n") + !out.contains("DA:0,") + out.contains("LH:2\n") + out.contains("LF:2\n") + } + + def "multiple files are sorted and lines within a file are sorted"() { + given: + def a = new LinesCoverage() + a.executableLines.set(2); a.executableLines.set(5) + a.coveredLines.set(5) + + def z = new LinesCoverage() + z.executableLines.set(1) + z.coveredLines.set(1) + + def m = new LinkedHashMap() + m.put("z/FileZ.java", z) + m.put("a/FileA.java", a) + + when: + def out = LcovReportWriter.toString(m) + + then: "files sorted: a/... then z/..." + def idxA = out.indexOf("SF:a/FileA.java\n") + def idxZ = out.indexOf("SF:z/FileZ.java\n") + idxA >= 0 && idxZ > idxA + + and: "lines sorted within a/FileA.java (2 then 5) with correct hit counts" + def blockA = out.substring(idxA, idxZ) + blockA.indexOf("DA:2,0\n") >= 0 && + blockA.indexOf("DA:5,1\n") > blockA.indexOf("DA:2,0\n") + } + + def "null LinesCoverage is treated as empty"() { + given: + def m = new LinkedHashMap() + m.put("empty.java", null) + + expect: + LcovReportWriter.toString(m) == """SF:empty.java +LH:0 +LF:0 +end_of_record +""" + } + + def "skips empty or null path entries"() { + given: + def lc = new LinesCoverage() + lc.executableLines.set(1); lc.coveredLines.set(1) + def m = new LinkedHashMap() + m.put("", lc) + m.put(null, lc) + + expect: + LcovReportWriter.toString(m) == "" + } + + def "covered lines outside executable set are ignored in DA and LH"() { + given: + def lc = new LinesCoverage() + lc.executableLines.set(1); lc.executableLines.set(2) + lc.coveredLines.set(3) // not executable -> ignored + def m = Collections.singletonMap("X.java", lc) + + expect: + LcovReportWriter.toString(m) == """SF:X.java +DA:1,0 +DA:2,0 +LH:0 +LF:2 +end_of_record +""" + } + + def "no executable lines even if covered bits exist -> no DA, LF=LH=0"() { + given: + def lc = new LinesCoverage() + lc.coveredLines.set(1); lc.coveredLines.set(2) + def m = Collections.singletonMap("Y.java", lc) + + expect: + LcovReportWriter.toString(m) == """SF:Y.java +LH:0 +LF:0 +end_of_record +""" + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/buildsystem/ProxyTestModuleTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/buildsystem/ProxyTestModuleTest.groovy index 93db3abc9d1..fe2f3573234 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/buildsystem/ProxyTestModuleTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/domain/buildsystem/ProxyTestModuleTest.groovy @@ -11,7 +11,7 @@ import datadog.trace.api.civisibility.coverage.CoverageStore import datadog.trace.api.civisibility.telemetry.CiVisibilityMetricCollector import datadog.trace.civisibility.codeowners.Codeowners import datadog.trace.civisibility.config.ExecutionSettings -import datadog.trace.civisibility.coverage.percentage.child.ChildProcessCoverageReporter +import datadog.trace.civisibility.coverage.report.child.ChildProcessCoverageReporter import datadog.trace.civisibility.decorator.TestDecorator import datadog.trace.civisibility.ipc.SignalClient import datadog.trace.civisibility.source.LinesResolver diff --git a/dd-java-agent/agent-ci-visibility/src/test/resources/datadog/trace/civisibility/config/settings-response.ftl b/dd-java-agent/agent-ci-visibility/src/test/resources/datadog/trace/civisibility/config/settings-response.ftl index f21b821434e..1add55398e5 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/resources/datadog/trace/civisibility/config/settings-response.ftl +++ b/dd-java-agent/agent-ci-visibility/src/test/resources/datadog/trace/civisibility/config/settings-response.ftl @@ -10,6 +10,7 @@ "flaky_test_retries_enabled": ${settings.flakyTestRetriesEnabled?c}, "impacted_tests_enabled": ${settings.impactedTestsDetectionEnabled?c}, "known_tests_enabled": ${settings.knownTestsEnabled?c}, + "coverage_report_upload_enabled": ${settings.coverageReportUploadEnabled?c}, <#if settings.defaultBranch??> "default_branch": "${settings.defaultBranch}", diff --git a/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityInstrumentationTest.groovy b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityInstrumentationTest.groovy index 14da640d573..39298a6860c 100644 --- a/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityInstrumentationTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityInstrumentationTest.groovy @@ -25,7 +25,7 @@ import datadog.trace.bootstrap.ContextStore import datadog.trace.civisibility.codeowners.Codeowners import datadog.trace.civisibility.config.* import datadog.trace.civisibility.coverage.file.FileCoverageStore -import datadog.trace.civisibility.coverage.percentage.NoOpCoverageCalculator +import datadog.trace.civisibility.coverage.report.NoOpCoverageProcessor import datadog.trace.civisibility.decorator.TestDecorator import datadog.trace.civisibility.decorator.TestDecoratorImpl import datadog.trace.civisibility.diff.Diff @@ -192,7 +192,7 @@ abstract class CiVisibilityInstrumentationTest extends AgentTestRunner { executionSettingsFactory, signalServer, repoIndexBuilder, - new NoOpCoverageCalculator.Factory() + new NoOpCoverageProcessor.Factory() ) } @@ -234,6 +234,7 @@ abstract class CiVisibilityInstrumentationTest extends AgentTestRunner { settings.itrEnabled, settings.flakyRetryEnabled, settings.impactedTestsDetectionEnabled, + false, earlyFlakinessDetectionSettings, testManagementSettings, settings.itrEnabled ? "itrCorrelationId" : null, diff --git a/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilitySmokeTest.groovy b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilitySmokeTest.groovy index 4251f586597..7fa37f7c919 100644 --- a/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilitySmokeTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilitySmokeTest.groovy @@ -52,4 +52,8 @@ abstract class CiVisibilitySmokeTest extends Specification { // an even more basic smoke check for distributions: assert that we received some assert !receivedTelemetryDistributions.isEmpty() } + + protected verifyCoverageReports(String projectName, List reports, Map replacements) { + CiVisibilityTestUtils.assertData(projectName, reports, replacements) + } } diff --git a/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTestUtils.groovy b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTestUtils.groovy index a6d32892086..86ea005d31e 100644 --- a/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTestUtils.groovy +++ b/dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilityTestUtils.groovy @@ -2,11 +2,7 @@ package datadog.trace.civisibility import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature -import com.jayway.jsonpath.Configuration -import com.jayway.jsonpath.JsonPath -import com.jayway.jsonpath.Option -import com.jayway.jsonpath.ReadContext -import com.jayway.jsonpath.WriteContext +import com.jayway.jsonpath.* import datadog.trace.api.DDSpanTypes import datadog.trace.api.civisibility.config.LibraryCapability import datadog.trace.api.civisibility.config.TestFQN @@ -19,12 +15,20 @@ import freemarker.template.TemplateExceptionHandler import org.opentest4j.AssertionFailedError import org.skyscreamer.jsonassert.JSONAssert import org.skyscreamer.jsonassert.JSONCompareMode +import org.w3c.dom.Document +import org.xmlunit.builder.DiffBuilder +import org.xmlunit.builder.Input +import org.xmlunit.diff.Diff +import org.xmlunit.util.Convert +import javax.xml.parsers.DocumentBuilderFactory import java.nio.file.Files import java.nio.file.Paths import java.util.regex.Pattern import java.util.stream.Collectors +import static org.junit.jupiter.api.Assertions.assertEquals + abstract class CiVisibilityTestUtils { static final List EVENT_DYNAMIC_PATHS = [ @@ -86,6 +90,44 @@ abstract class CiVisibilityTestUtils { Files.write(Paths.get(baseTemplatesPath, "coverages.ftl"), templateGenerator.generateTemplate(coverages, COVERAGE_DYNAMIC_PATHS + compiledAdditionalReplacements).bytes) } + static void assertData(String baseTemplatesPath, List reports, Map replacements) { + def expectedReportEvent = getFreemarkerTemplate(baseTemplatesPath + "/coverage_report_event.ftl", replacements) + def actualReportEvent = JSON_MAPPER.writeValueAsString(reports[0].event) + + compareJson(expectedReportEvent, actualReportEvent) + + def expectedReport = getFreemarkerTemplate(baseTemplatesPath + "/coverage_report.ftl", replacements) + def actualReport = reports[0].report + + if (expectedReport.contains(" assertData(String baseTemplatesPath, List> events, List> coverages, Map additionalReplacements, List ignoredTags, List additionalDynamicPaths = []) { events.sort(EVENT_RESOURCE_COMPARATOR) @@ -103,40 +145,34 @@ abstract class CiVisibilityTestUtils { // ignore provided tags events = removeTags(events, ignoredTags) - def environment = System.getenv() - def ciRun = environment.get("GITHUB_ACTION") != null || environment.get("GITLAB_CI") != null - def comparisonMode = ciRun ? JSONCompareMode.LENIENT : JSONCompareMode.NON_EXTENSIBLE - def expectedEvents = getFreemarkerTemplate(baseTemplatesPath + "/events.ftl", replacementMap, events) def actualEvents = JSON_MAPPER.writeValueAsString(events) - try { - JSONAssert.assertEquals(expectedEvents, actualEvents, comparisonMode) - } catch (AssertionError e) { - if (ciRun) { - // When running in CI the assertion error message does not contain the actual diff, - // so we print the events to the console to help debug the issue - println "Expected events: $expectedEvents" - println "Actual events: $actualEvents" - } - throw new AssertionFailedError("Events mismatch", expectedEvents, actualEvents, e) - } + compareJson(expectedEvents, actualEvents) def expectedCoverages = getFreemarkerTemplate(baseTemplatesPath + "/coverages.ftl", replacementMap, coverages) def actualCoverages = JSON_MAPPER.writeValueAsString(coverages) + compareJson(expectedCoverages, actualCoverages) + + return replacementMap + } + + private static void compareJson(String expectedJson, String actualJson) { + def environment = System.getenv() + def ciRun = environment.get("GITHUB_ACTION") != null || environment.get("GITLAB_CI") != null + def comparisonMode = ciRun ? JSONCompareMode.LENIENT : JSONCompareMode.NON_EXTENSIBLE + try { - JSONAssert.assertEquals(expectedCoverages, actualCoverages, comparisonMode) + JSONAssert.assertEquals(expectedJson, actualJson, comparisonMode) } catch (AssertionError e) { if (ciRun) { // When running in CI the assertion error message does not contain the actual diff, // so we print the events to the console to help debug the issue - println "Expected coverages: $expectedCoverages" - println "Actual coverages: $actualCoverages" + println "Expected JSON: $expectedJson" + println "Actual JSON: $actualJson" } - throw new AssertionFailedError("Coverages mismatch", expectedCoverages, actualCoverages, e) + throw new AssertionFailedError("Coverage report events mismatch", expectedJson, actualJson, e) } - - return replacementMap } static boolean assertTestsOrder(List> events, List expectedOrder) { @@ -344,4 +380,14 @@ abstract class CiVisibilityTestUtils { this.unique = unique } } + + static final class CoverageReport { + final Map event + final String report + + CoverageReport(Map event, String report) { + this.event = event + this.report = report + } + } } diff --git a/dd-java-agent/instrumentation/gradle-3.0/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleProjectConfigurator.groovy b/dd-java-agent/instrumentation/gradle-3.0/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleProjectConfigurator.groovy index 6031671c785..2065a267ba7 100644 --- a/dd-java-agent/instrumentation/gradle-3.0/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleProjectConfigurator.groovy +++ b/dd-java-agent/instrumentation/gradle-3.0/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleProjectConfigurator.groovy @@ -186,7 +186,12 @@ class GradleProjectConfigurator { private void configureJacoco(Project project, BuildSessionSettings sessionSettings) { def config = Config.get() - if (!config.isCiVisibilityJacocoPluginVersionProvided() || project.plugins.hasPlugin(JACOCO_PLUGIN_ID)) { + if (project.plugins.hasPlugin(JACOCO_PLUGIN_ID)) { + // Jacoco is already configured for this project + return + } + + if (!config.isCiVisibilityJacocoPluginVersionProvided() && !sessionSettings.isCoverageReportUploadEnabled()) { return } diff --git a/dd-java-agent/instrumentation/gradle-8.3/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityService.java b/dd-java-agent/instrumentation/gradle-8.3/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityService.java index 2352fa3aead..adc27283cb0 100644 --- a/dd-java-agent/instrumentation/gradle-8.3/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityService.java +++ b/dd-java-agent/instrumentation/gradle-8.3/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityService.java @@ -41,7 +41,8 @@ public String getCompilerPluginVersion() { } public boolean isJacocoInjectionEnabled() { - return config.isCiVisibilityJacocoPluginVersionProvided(); + return config.isCiVisibilityJacocoPluginVersionProvided() + || buildEventsHandler.getSessionSettings(SESSION_KEY).isCoverageReportUploadEnabled(); } public String getJacocoVersion() { diff --git a/dd-java-agent/instrumentation/maven-3.2.1/src/main/java/datadog/trace/instrumentation/maven3/MavenProjectConfigurator.java b/dd-java-agent/instrumentation/maven-3.2.1/src/main/java/datadog/trace/instrumentation/maven3/MavenProjectConfigurator.java index 13758cefd1e..73937478165 100644 --- a/dd-java-agent/instrumentation/maven-3.2.1/src/main/java/datadog/trace/instrumentation/maven3/MavenProjectConfigurator.java +++ b/dd-java-agent/instrumentation/maven-3.2.1/src/main/java/datadog/trace/instrumentation/maven3/MavenProjectConfigurator.java @@ -245,8 +245,12 @@ private static Xpp3Dom addAnnotationProcessorPath( void configureJacoco( MavenSession session, MavenProject project, BuildSessionSettings sessionSettings) { excludeDatadogClassLoaderFromJacocoInstrumentation(project); + if (runsWithJacoco(session, project)) { + // Jacoco is already configured for this project + return; + } if (Config.get().isCiVisibilityJacocoPluginVersionProvided() - && !runsWithJacoco(session, project)) { + || sessionSettings.isCoverageReportUploadEnabled()) { configureJacocoPlugin(project, sessionSettings); } } diff --git a/dd-smoke-tests/backend-mock/src/main/groovy/datadog/smoketest/MockBackend.groovy b/dd-smoke-tests/backend-mock/src/main/groovy/datadog/smoketest/MockBackend.groovy index a364dd0a996..de2890c9e26 100644 --- a/dd-smoke-tests/backend-mock/src/main/groovy/datadog/smoketest/MockBackend.groovy +++ b/dd-smoke-tests/backend-mock/src/main/groovy/datadog/smoketest/MockBackend.groovy @@ -1,17 +1,20 @@ package datadog.smoketest -import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer - import com.fasterxml.jackson.databind.ObjectMapper import datadog.trace.agent.test.server.http.TestHttpServer +import datadog.trace.civisibility.CiVisibilityTestUtils import datadog.trace.test.util.MultipartRequestParser +import org.apache.commons.fileupload.FileItem +import org.apache.commons.io.IOUtils +import org.msgpack.jackson.dataformat.MessagePackFactory +import spock.util.concurrent.PollingConditions + import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.CopyOnWriteArrayList import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream -import org.apache.commons.io.IOUtils -import org.msgpack.jackson.dataformat.MessagePackFactory -import spock.util.concurrent.PollingConditions + +import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer class MockBackend implements AutoCloseable { @@ -27,6 +30,8 @@ class MockBackend implements AutoCloseable { private final Queue> receivedTelemetryDistributions = new ConcurrentLinkedQueue<>() private final Queue> receivedLogs = new ConcurrentLinkedQueue<>() + private final Queue>> receivedCoverageReports = new ConcurrentLinkedQueue<>() + private final Collection> skippableTests = new CopyOnWriteArrayList<>() private final Collection> flakyTests = new CopyOnWriteArrayList<>() private final Collection> knownTests = new CopyOnWriteArrayList<>() @@ -39,6 +44,7 @@ class MockBackend implements AutoCloseable { private boolean impactedTestsDetectionEnabled = false private boolean knownTestsEnabled = false private boolean testManagementEnabled = false + private boolean codeCoverageReportUploadEnabled = false private int attemptToFixRetries = 0 void reset() { @@ -47,6 +53,7 @@ class MockBackend implements AutoCloseable { receivedTelemetryMetrics.clear() receivedTelemetryDistributions.clear() receivedLogs.clear() + receivedCoverageReports.clear() skippableTests.clear() flakyTests.clear() @@ -60,6 +67,7 @@ class MockBackend implements AutoCloseable { impactedTestsDetectionEnabled = false knownTestsEnabled = false testManagementEnabled = false + codeCoverageReportUploadEnabled = false attemptToFixRetries = 0 } @@ -100,6 +108,10 @@ class MockBackend implements AutoCloseable { this.testManagementEnabled = testManagementEnabled } + void givenCodeCoverageReportUpload(boolean codeCoverageReportUploadEnabled) { + this.codeCoverageReportUploadEnabled = codeCoverageReportUploadEnabled + } + void givenAttemptToFixRetries(int attemptToFixRetries) { this.attemptToFixRetries = attemptToFixRetries } @@ -177,6 +189,7 @@ class MockBackend implements AutoCloseable { "flaky_test_retries_enabled": $flakyRetriesEnabled, "impacted_tests_enabled": $impactedTestsDetectionEnabled, "known_tests_enabled": $knownTestsEnabled, + "coverage_report_upload_enabled": $codeCoverageReportUploadEnabled, "test_management": { "enabled": $testManagementEnabled, "attempt_to_fix_retries": $attemptToFixRetries @@ -332,6 +345,12 @@ class MockBackend implements AutoCloseable { response.status(200).send() } + + prefix('/api/v2/cicovreprt') { + def parsed = MultipartRequestParser.parseRequest(request.body, request.headers.get("Content-Type")) + receivedCoverageReports.add(parsed) + response.status(200).send() + } } } @@ -419,6 +438,22 @@ class MockBackend implements AutoCloseable { return logs } + List waitForCoverageReports(int expectedCount) { + def receiveConditions = new PollingConditions(timeout: 15, initialDelay: 1, delay: 0.5, factor: 1) + receiveConditions.eventually { + assert receivedCoverageReports.size() == expectedCount + } + + List reports = new ArrayList<>() + while (!receivedCoverageReports.isEmpty()) { + def multipartRequest = receivedCoverageReports.poll() + Map event = JSON_MAPPER.readValue(multipartRequest["event"].get(0).get(), Map) + String report = new String(decompress(multipartRequest["coverage"].get(0).get())) + reports.add(new CiVisibilityTestUtils.CoverageReport(event, report)) + } + return reports + } + List> getAllReceivedTelemetryMetrics() { return new ArrayList(receivedTelemetryMetrics) } diff --git a/dd-smoke-tests/gradle/src/test/resources/test-failed-flaky-retries/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-failed-flaky-retries/events.ftl index 8fb894266ae..ea5291c29a2 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-failed-flaky-retries/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-failed-flaky-retries/events.ftl @@ -240,6 +240,7 @@ "error" : 1, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -302,6 +303,7 @@ "error" : 1, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -366,6 +368,7 @@ "error" : 1, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -430,6 +433,7 @@ "error" : 1, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -494,6 +498,7 @@ "error" : 1, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -640,4 +645,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/gradle/src/test/resources/test-failed-legacy-instrumentation/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-failed-legacy-instrumentation/events.ftl index 686f52c3af4..4bd4970350e 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-failed-legacy-instrumentation/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-failed-legacy-instrumentation/events.ftl @@ -188,6 +188,7 @@ "error" : 1, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -383,4 +384,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/gradle/src/test/resources/test-failed-new-instrumentation/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-failed-new-instrumentation/events.ftl index f60c9131589..fdaa0055a7b 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-failed-new-instrumentation/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-failed-new-instrumentation/events.ftl @@ -240,6 +240,7 @@ "error" : 1, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -383,4 +384,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/events.ftl index 527ee76d9dc..f327423fecd 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/events.ftl @@ -234,6 +234,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-junit-5/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-succeed-junit-5/events.ftl index d5db1127f85..ec8f9da06fd 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-junit-5/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-junit-5/events.ftl @@ -236,6 +236,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -297,6 +298,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -441,4 +443,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-legacy-instrumentation/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-succeed-legacy-instrumentation/events.ftl index 9c28dbea433..a277f6f1bf6 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-legacy-instrumentation/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-legacy-instrumentation/events.ftl @@ -186,6 +186,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -246,6 +247,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -439,4 +441,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-forks-legacy-instrumentation/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-forks-legacy-instrumentation/events.ftl index 3e09ab667c8..9fdf3a28b66 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-forks-legacy-instrumentation/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-forks-legacy-instrumentation/events.ftl @@ -185,6 +185,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -294,6 +295,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -485,4 +487,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-forks-new-instrumentation/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-forks-new-instrumentation/events.ftl index a36672985a7..c35fa2f919c 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-forks-new-instrumentation/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-forks-new-instrumentation/events.ftl @@ -234,6 +234,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -343,6 +344,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -485,4 +487,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-module-legacy-instrumentation/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-module-legacy-instrumentation/events.ftl index 999f7f04106..b2d6b1d000b 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-module-legacy-instrumentation/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-module-legacy-instrumentation/events.ftl @@ -192,6 +192,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -252,6 +253,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -703,4 +705,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-module-new-instrumentation/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-module-new-instrumentation/events.ftl index 85a0f8abdad..91ae16cba3e 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-module-new-instrumentation/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-multi-module-new-instrumentation/events.ftl @@ -241,6 +241,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -301,6 +302,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -703,4 +705,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-new-instrumentation/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-succeed-new-instrumentation/events.ftl index 5cb427be12e..c1fdd78ad33 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-new-instrumentation/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-new-instrumentation/events.ftl @@ -236,6 +236,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -296,6 +297,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -439,4 +441,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-old-gradle/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-succeed-old-gradle/events.ftl index 5111b76f994..3b4664f9804 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-old-gradle/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-old-gradle/events.ftl @@ -186,6 +186,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -246,6 +247,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", @@ -439,4 +441,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy b/dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy index 79e09eb9105..b8dfc7ba1fc 100644 --- a/dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy +++ b/dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy @@ -7,18 +7,6 @@ import datadog.trace.api.config.CiVisibilityConfig import datadog.trace.api.config.GeneralConfig import datadog.trace.civisibility.CiVisibilitySmokeTest import datadog.trace.util.Strings -import spock.lang.IgnoreIf - -import java.nio.file.FileVisitResult -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.nio.file.SimpleFileVisitor -import java.nio.file.attribute.BasicFileAttributes -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeoutException -import javax.xml.parsers.DocumentBuilder -import javax.xml.parsers.DocumentBuilderFactory import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response @@ -29,10 +17,18 @@ import org.slf4j.LoggerFactory import org.w3c.dom.Document import org.w3c.dom.NodeList import spock.lang.AutoCleanup +import spock.lang.IgnoreIf import spock.lang.Shared import spock.lang.TempDir import spock.util.environment.Jvm +import javax.xml.parsers.DocumentBuilder +import javax.xml.parsers.DocumentBuilderFactory +import java.nio.file.* +import java.nio.file.attribute.BasicFileAttributes +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException + class MavenSmokeTest extends CiVisibilitySmokeTest { private static final Logger LOGGER = LoggerFactory.getLogger(MavenSmokeTest) @@ -80,6 +76,11 @@ class MavenSmokeTest extends CiVisibilitySmokeTest { mockBackend.givenImpactedTestsDetection(true) + def coverageReportExpected = jacocoCoverage && CiVisibilitySmokeTest.classLoader.getResource(projectName + "/coverage_report_event.ftl") != null + if (coverageReportExpected) { + mockBackend.givenCodeCoverageReportUpload(true) + } + def agentArgs = jacocoCoverage ? [ "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_JACOCO_PLUGIN_VERSION)}=${JACOCO_PLUGIN_VERSION}" as String ] : [] @@ -94,6 +95,12 @@ class MavenSmokeTest extends CiVisibilitySmokeTest { verifyEventsAndCoverages(projectName, "maven", mavenVersion, mockBackend.waitForEvents(expectedEvents), mockBackend.waitForCoverages(expectedCoverages)) verifyTelemetryMetrics(mockBackend.getAllReceivedTelemetryMetrics(), mockBackend.getAllReceivedTelemetryDistributions(), expectedEvents) + if (coverageReportExpected) { + def reports = mockBackend.waitForCoverageReports(1) + def realProjectHome = projectHome.toRealPath().toString() + verifyCoverageReports(projectName, reports, ["ci_workspace_path": realProjectHome]) + } + where: projectName | mavenVersion | expectedEvents | expectedCoverages | expectSuccess | testsSkipping | flakyRetries | jacocoCoverage | commandLineParams | minSupportedJavaVersion "test_successful_maven_run" | "3.5.4" | 5 | 1 | true | true | false | true | [] | 8 @@ -395,6 +402,7 @@ class MavenSmokeTest extends CiVisibilitySmokeTest { "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_GIT_UPLOAD_ENABLED)}=false," + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_COMPILER_PLUGIN_VERSION)}=${JAVAC_PLUGIN_VERSION}," + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_AGENTLESS_URL)}=${mockBackend.intakeUrl}," + + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_INTAKE_AGENTLESS_URL)}=${mockBackend.intakeUrl}," + "${Strings.propertyNameToSystemPropertyName(CiVisibilityConfig.CIVISIBILITY_FLAKY_RETRY_ONLY_KNOWN_FLAKES)}=true," if (setServiceName) { diff --git a/dd-smoke-tests/maven/src/test/resources/test_failed_maven_run_flaky_retries/coverage_report.ftl b/dd-smoke-tests/maven/src/test/resources/test_failed_maven_run_flaky_retries/coverage_report.ftl new file mode 100644 index 00000000000..ce6176b2e99 --- /dev/null +++ b/dd-smoke-tests/maven/src/test/resources/test_failed_maven_run_flaky_retries/coverage_report.ftl @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dd-smoke-tests/maven/src/test/resources/test_failed_maven_run_flaky_retries/coverage_report_event.ftl b/dd-smoke-tests/maven/src/test/resources/test_failed_maven_run_flaky_retries/coverage_report_event.ftl new file mode 100644 index 00000000000..b8c2a2a9de9 --- /dev/null +++ b/dd-smoke-tests/maven/src/test/resources/test_failed_maven_run_flaky_retries/coverage_report_event.ftl @@ -0,0 +1,5 @@ +{ + "format" : "jacoco", + "type" : "coverage_report", + "ci.workspace_path" : "${ci_workspace_path}" +} diff --git a/dd-smoke-tests/maven/src/test/resources/test_failed_maven_run_flaky_retries/events.ftl b/dd-smoke-tests/maven/src/test/resources/test_failed_maven_run_flaky_retries/events.ftl index 3663a2bd36e..1c032d9aecf 100644 --- a/dd-smoke-tests/maven/src/test/resources/test_failed_maven_run_flaky_retries/events.ftl +++ b/dd-smoke-tests/maven/src/test/resources/test_failed_maven_run_flaky_retries/events.ftl @@ -306,6 +306,7 @@ "error" : 1, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -369,6 +370,7 @@ "error" : 1, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -434,6 +436,7 @@ "error" : 1, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -499,6 +502,7 @@ "error" : 1, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -564,6 +568,7 @@ "error" : 1, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -624,4 +629,4 @@ }, "type" : "test", "version" : 2 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run/coverage_report.ftl b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run/coverage_report.ftl new file mode 100644 index 00000000000..4a77bfd57c6 --- /dev/null +++ b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run/coverage_report.ftl @@ -0,0 +1,7 @@ +SF:src/main/java/datadog/smoke/Calculator.java +DA:3,0 +DA:5,1 +DA:9,1 +LH:2 +LF:3 +end_of_record diff --git a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run/coverage_report_event.ftl b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run/coverage_report_event.ftl new file mode 100644 index 00000000000..d439f492d35 --- /dev/null +++ b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run/coverage_report_event.ftl @@ -0,0 +1,5 @@ +{ + "format" : "lcov", + "type" : "coverage_report", + "ci.workspace_path" : "${ci_workspace_path}" +} diff --git a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run/events.ftl b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run/events.ftl index 4c68a894536..0bf2338a889 100644 --- a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run/events.ftl +++ b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run/events.ftl @@ -310,6 +310,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -371,6 +372,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -428,4 +430,4 @@ }, "type" : "test", "version" : 2 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_builtin_coverage/events.ftl b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_builtin_coverage/events.ftl index 5f7cce4a596..4e61ba52a39 100644 --- a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_builtin_coverage/events.ftl +++ b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_builtin_coverage/events.ftl @@ -274,6 +274,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -335,6 +336,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -392,4 +394,4 @@ }, "type" : "test", "version" : 2 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_junit_platform_runner/events.ftl b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_junit_platform_runner/events.ftl index 822de2307e4..b2a9fdb241b 100644 --- a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_junit_platform_runner/events.ftl +++ b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_junit_platform_runner/events.ftl @@ -327,6 +327,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.impacted_tests" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", diff --git a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_multiple_forks/events.ftl b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_multiple_forks/events.ftl index 5b1e5a20fa6..252ba824ece 100644 --- a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_multiple_forks/events.ftl +++ b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_multiple_forks/events.ftl @@ -310,6 +310,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -371,6 +372,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -428,4 +430,4 @@ }, "type" : "test", "version" : 2 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_surefire_3_0_0/events.ftl b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_surefire_3_0_0/events.ftl index 4c68a894536..0bf2338a889 100644 --- a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_surefire_3_0_0/events.ftl +++ b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_surefire_3_0_0/events.ftl @@ -310,6 +310,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -371,6 +372,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -428,4 +430,4 @@ }, "type" : "test", "version" : 2 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_surefire_3_5_0/events.ftl b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_surefire_3_5_0/events.ftl index 4c68a894536..0bf2338a889 100644 --- a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_surefire_3_5_0/events.ftl +++ b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_surefire_3_5_0/events.ftl @@ -310,6 +310,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -371,6 +372,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -428,4 +430,4 @@ }, "type" : "test", "version" : 2 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_with_arg_line_property/events.ftl b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_with_arg_line_property/events.ftl index efac85e2237..4127ce74a85 100644 --- a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_with_arg_line_property/events.ftl +++ b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_with_arg_line_property/events.ftl @@ -266,6 +266,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -321,4 +322,4 @@ }, "type" : "test", "version" : 2 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_with_cucumber/events.ftl b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_with_cucumber/events.ftl index fa8d74caf57..87a11c6f035 100644 --- a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_with_cucumber/events.ftl +++ b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_with_cucumber/events.ftl @@ -364,6 +364,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.test_impact_analysis" : "1", "_dd.library_capabilities.test_management.attempt_to_fix" : "5", @@ -447,4 +448,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_with_jacoco_and_argline/events.ftl b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_with_jacoco_and_argline/events.ftl index 5d23ecff637..529e4948018 100644 --- a/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_with_jacoco_and_argline/events.ftl +++ b/dd-smoke-tests/maven/src/test/resources/test_successful_maven_run_with_jacoco_and_argline/events.ftl @@ -310,6 +310,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -371,6 +372,7 @@ "error" : 0, "meta" : { "_dd.library_capabilities.auto_test_retries" : "1", + "_dd.library_capabilities.coverage_report_upload" : "1", "_dd.library_capabilities.early_flake_detection" : "1", "_dd.library_capabilities.fail_fast_test_order" : "1", "_dd.library_capabilities.impacted_tests" : "1", @@ -428,4 +430,4 @@ }, "type" : "test", "version" : 2 -} ] \ No newline at end of file +} ] diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java index e39832f1432..3aa72841201 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java @@ -8,6 +8,8 @@ public final class CiVisibilityConfig { "civisibility.trace.sanitation.enabled"; public static final String CIVISIBILITY_AGENTLESS_ENABLED = "civisibility.agentless.enabled"; public static final String CIVISIBILITY_AGENTLESS_URL = "civisibility.agentless.url"; + public static final String CIVISIBILITY_INTAKE_AGENTLESS_URL = + "civisibility.intake.agentless.url"; public static final String CIVISIBILITY_SOURCE_DATA_ENABLED = "civisibility.source.data.enabled"; public static final String CIVISIBILITY_BUILD_INSTRUMENTATION_ENABLED = "civisibility.build.instrumentation.enabled"; @@ -106,6 +108,8 @@ public final class CiVisibilityConfig { public static final String CIVISIBILITY_JACOCO_PLUGIN_VERSION = "civisibility.jacoco.plugin.version"; public static final String CIVISIBILITY_GRADLE_SOURCE_SETS = "civisibility.gradle.sourcesets"; + public static final String CIVISIBILITY_CODE_COVERAGE_REPORT_UPLOAD_ENABLED = + "civisibility.code.coverage.report.upload.enabled"; public static final String TEST_SESSION_NAME = "test.session.name"; diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 36a165ee1c3..074edf0c62b 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -225,6 +225,7 @@ import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_CODE_COVERAGE_INCLUDES; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_CODE_COVERAGE_LINES_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_CODE_COVERAGE_REPORT_DUMP_DIR; +import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_CODE_COVERAGE_REPORT_UPLOAD_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_CODE_COVERAGE_ROOT_PACKAGES_LIMIT; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_COMPILER_PLUGIN_AUTO_CONFIGURATION_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_COMPILER_PLUGIN_VERSION; @@ -245,6 +246,7 @@ import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_GRADLE_SOURCE_SETS; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_INJECTED_TRACER_VERSION; +import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_INTAKE_AGENTLESS_URL; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_ITR_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_JACOCO_PLUGIN_VERSION; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_JVM_INFO_CACHE_SIZE; @@ -966,6 +968,7 @@ public static String getHostName() { private final boolean ciVisibilityTraceSanitationEnabled; private final boolean ciVisibilityAgentlessEnabled; private final String ciVisibilityAgentlessUrl; + private final String ciVisibilityIntakeAgentlessUrl; private final boolean ciVisibilitySourceDataEnabled; private final boolean ciVisibilityBuildInstrumentationEnabled; @@ -984,6 +987,7 @@ public static String getHostName() { private final String[] ciVisibilityCodeCoverageIncludedPackages; private final String[] ciVisibilityCodeCoverageExcludedPackages; private final List ciVisibilityJacocoGradleSourceSets; + private final boolean ciVisibilityCodeCoverageReportUploadEnabled; private final Integer ciVisibilityDebugPort; private final boolean ciVisibilityGitClientEnabled; private final boolean ciVisibilityGitUploadEnabled; @@ -2166,20 +2170,13 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment()) DEFAULT_CIVISIBILITY_BUILD_INSTRUMENTATION_ENABLED); final String ciVisibilityAgentlessUrlStr = configProvider.getString(CIVISIBILITY_AGENTLESS_URL); - URI parsedCiVisibilityUri = null; - if (ciVisibilityAgentlessUrlStr != null && !ciVisibilityAgentlessUrlStr.isEmpty()) { - try { - parsedCiVisibilityUri = new URL(ciVisibilityAgentlessUrlStr).toURI(); - } catch (MalformedURLException | URISyntaxException ex) { - log.error( - "Cannot parse CI Visibility agentless URL '{}', skipping", ciVisibilityAgentlessUrlStr); - } - } - if (parsedCiVisibilityUri != null) { - ciVisibilityAgentlessUrl = ciVisibilityAgentlessUrlStr; - } else { - ciVisibilityAgentlessUrl = null; - } + ciVisibilityAgentlessUrl = + isValidUrl(ciVisibilityAgentlessUrlStr) ? ciVisibilityAgentlessUrlStr : null; + + final String ciVisibilityIntakeAgentlessUrlStr = + configProvider.getString(CIVISIBILITY_INTAKE_AGENTLESS_URL); + ciVisibilityIntakeAgentlessUrl = + isValidUrl(ciVisibilityIntakeAgentlessUrlStr) ? ciVisibilityIntakeAgentlessUrlStr : null; ciVisibilityAgentJarUri = configProvider.getString(CIVISIBILITY_AGENT_JAR_URI); ciVisibilityAutoConfigurationEnabled = @@ -2221,6 +2218,8 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment()) convertJacocoExclusionFormatToPackagePrefixes(ciVisibilityCodeCoverageExcludes); ciVisibilityJacocoGradleSourceSets = configProvider.getList(CIVISIBILITY_GRADLE_SOURCE_SETS, Arrays.asList("main", "test")); + ciVisibilityCodeCoverageReportUploadEnabled = + configProvider.getBoolean(CIVISIBILITY_CODE_COVERAGE_REPORT_UPLOAD_ENABLED, true); ciVisibilityDebugPort = configProvider.getInteger(CIVISIBILITY_DEBUG_PORT); ciVisibilityGitClientEnabled = configProvider.getBoolean(CIVISIBILITY_GIT_CLIENT_ENABLED, true); ciVisibilityGitUploadEnabled = @@ -2747,6 +2746,19 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment()) log.debug("New instance: {}", this); } + private static boolean isValidUrl(String url) { + if (url == null || url.isEmpty()) { + return false; + } + try { + new URL(url).toURI(); + return true; + } catch (MalformedURLException | URISyntaxException ex) { + log.error("Cannot parse URL '{}', skipping", url); + return false; + } + } + private RumInjectorConfig parseRumConfig(ConfigProvider configProvider) { if (!instrumenterConfig.isRumEnabled()) { return null; @@ -3669,6 +3681,10 @@ public String getCiVisibilityAgentlessUrl() { return ciVisibilityAgentlessUrl; } + public String getCiVisibilityIntakeAgentlessUrl() { + return ciVisibilityIntakeAgentlessUrl; + } + public boolean isCiVisibilitySourceDataEnabled() { return ciVisibilitySourceDataEnabled; } @@ -3758,6 +3774,10 @@ public List getCiVisibilityJacocoGradleSourceSets() { return ciVisibilityJacocoGradleSourceSets; } + public boolean isCiVisibilityCodeCoverageReportUploadEnabled() { + return ciVisibilityCodeCoverageReportUploadEnabled; + } + public Integer getCiVisibilityDebugPort() { return ciVisibilityDebugPort; } diff --git a/internal-api/src/main/java/datadog/trace/api/civisibility/config/LibraryCapability.java b/internal-api/src/main/java/datadog/trace/api/civisibility/config/LibraryCapability.java index f4ed209ae30..be3aca538ff 100644 --- a/internal-api/src/main/java/datadog/trace/api/civisibility/config/LibraryCapability.java +++ b/internal-api/src/main/java/datadog/trace/api/civisibility/config/LibraryCapability.java @@ -8,7 +8,8 @@ public enum LibraryCapability { FAIL_FAST("fail_fast_test_order", "1"), QUARANTINE("test_management.quarantine", "1"), DISABLED("test_management.disable", "1"), - ATTEMPT_TO_FIX("test_management.attempt_to_fix", "5"); + ATTEMPT_TO_FIX("test_management.attempt_to_fix", "5"), + COV_REPORT_UPLOAD("coverage_report_upload", "1"); private final String tag; private final String version; diff --git a/internal-api/src/main/java/datadog/trace/api/civisibility/domain/BuildSessionSettings.java b/internal-api/src/main/java/datadog/trace/api/civisibility/domain/BuildSessionSettings.java index bc9d4928aff..76d6c33a7ed 100644 --- a/internal-api/src/main/java/datadog/trace/api/civisibility/domain/BuildSessionSettings.java +++ b/internal-api/src/main/java/datadog/trace/api/civisibility/domain/BuildSessionSettings.java @@ -4,15 +4,23 @@ public class BuildSessionSettings { + private final boolean coverageReportUploadEnabled; private final List coverageIncludedPackages; private final List coverageExcludedPackages; public BuildSessionSettings( - List coverageIncludedPackages, List coverageExcludedPackages) { + boolean coverageReportUploadEnabled, + List coverageIncludedPackages, + List coverageExcludedPackages) { + this.coverageReportUploadEnabled = coverageReportUploadEnabled; this.coverageIncludedPackages = coverageIncludedPackages; this.coverageExcludedPackages = coverageExcludedPackages; } + public boolean isCoverageReportUploadEnabled() { + return coverageReportUploadEnabled; + } + public List getCoverageIncludedPackages() { return coverageIncludedPackages; } diff --git a/internal-api/src/main/java/datadog/trace/api/civisibility/telemetry/CiVisibilityCountMetric.java b/internal-api/src/main/java/datadog/trace/api/civisibility/telemetry/CiVisibilityCountMetric.java index 868ecde90b8..351c7e852e6 100644 --- a/internal-api/src/main/java/datadog/trace/api/civisibility/telemetry/CiVisibilityCountMetric.java +++ b/internal-api/src/main/java/datadog/trace/api/civisibility/telemetry/CiVisibilityCountMetric.java @@ -167,7 +167,12 @@ public enum CiVisibilityCountMetric { TEST_MANAGEMENT_TESTS_REQUEST("test_management.request", RequestCompressed.class), /** The number of tests requests sent to the test management tests endpoint that errored */ TEST_MANAGEMENT_TESTS_REQUEST_ERRORS( - "test_management.request_errors", ErrorType.class, StatusCode.class); + "test_management.request_errors", ErrorType.class, StatusCode.class), + /** The number of coverage upload requests sent */ + COVERAGE_UPLOAD_REQUEST("coverage_upload.request", RequestCompressed.class), + /** The number of coverage upload requests that errored */ + COVERAGE_UPLOAD_REQUEST_ERRORS( + "coverage_upload.request_errors", ErrorType.class, StatusCode.class); // need a "holder" class, as accessing static fields from enum constructors is illegal static class IndexHolder { diff --git a/internal-api/src/main/java/datadog/trace/api/civisibility/telemetry/CiVisibilityDistributionMetric.java b/internal-api/src/main/java/datadog/trace/api/civisibility/telemetry/CiVisibilityDistributionMetric.java index 8dcaa6eb385..b8a4bbb7c1c 100644 --- a/internal-api/src/main/java/datadog/trace/api/civisibility/telemetry/CiVisibilityDistributionMetric.java +++ b/internal-api/src/main/java/datadog/trace/api/civisibility/telemetry/CiVisibilityDistributionMetric.java @@ -39,7 +39,7 @@ public enum CiVisibilityDistributionMetric { "itr_skippable_tests.response_bytes", ResponseCompressed.class), /** The number of files covered inside a coverage payload */ CODE_COVERAGE_FILES("code_coverage.files"), - /* The time it takes to get the response of the known tests endpoint request in ms */ + /** The time it takes to get the response of the known tests endpoint request in ms */ KNOWN_TESTS_REQUEST_MS("known_tests.request_ms"), /** The number of bytes received by the known tests endpoint */ KNOWN_TESTS_RESPONSE_BYTES("known_tests.response_bytes", ResponseCompressed.class), @@ -56,7 +56,11 @@ public enum CiVisibilityDistributionMetric { /** The number of bytes received by the test management tests endpoint */ TEST_MANAGEMENT_TESTS_RESPONSE_BYTES("test_management.response_bytes", ResponseCompressed.class), /** The number of tests received by the test management tests endpoint */ - TEST_MANAGEMENT_TESTS_RESPONSE_TESTS("test_management.response_tests"); + TEST_MANAGEMENT_TESTS_RESPONSE_TESTS("test_management.response_tests"), + /** The time it takes to make a coverage upload request in ms */ + COVERAGE_UPLOAD_REQUEST_MS("coverage_upload.request_ms"), + /** The size of a coverage upload request in bytes */ + COVERAGE_UPLOAD_REQUEST_BYTES("coverage_upload.request_bytes", ResponseCompressed.class); private static final String NAMESPACE = "civisibility";