From 6d991410e2e2fca9cbe55a006d6214c4a053cf34 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Thu, 28 May 2026 10:45:56 +0200 Subject: [PATCH 1/2] refactor: config API dto package --- .../civisibility/config/ConfigurationApi.java | 1 + .../config/ConfigurationApiImpl.java | 232 +++++------------- .../config/ExecutionSettingsFactoryImpl.java | 1 + .../config/FileBasedConfigurationApi.java | 110 ++++----- .../config/TestManagementTestsDto.java | 48 ---- .../civisibility/config/api/dto/Data.java | 17 ++ .../civisibility/config/api/dto/Envelope.java | 9 + .../config/api/dto/MultiEnvelope.java | 15 ++ .../civisibility/config/api/dto/PageInfo.java | 33 +++ .../api/dto/request/KnownTestsRequest.java | 23 ++ .../dto/request/TestManagementRequest.java | 24 ++ .../dto/request}/TracerEnvironment.java | 2 +- .../api/dto/response/KnownTestsResponse.java | 34 +++ .../dto/response/Meta.java} | 20 +- .../dto/response}/TestIdentifierJson.java | 2 +- .../response/TestManagementTestsResponse.java | 49 ++++ .../config/ConfigurationApiImplTest.groovy | 1 + .../config/TracerEnvironmentTest.groovy | 1 + .../config/FileBasedConfigurationApiTest.java | 1 + 19 files changed, 324 insertions(+), 299 deletions(-) delete mode 100644 dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TestManagementTestsDto.java create mode 100644 dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/Data.java create mode 100644 dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/Envelope.java create mode 100644 dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/MultiEnvelope.java create mode 100644 dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/PageInfo.java create mode 100644 dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/request/KnownTestsRequest.java create mode 100644 dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/request/TestManagementRequest.java rename dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/{ => api/dto/request}/TracerEnvironment.java (98%) create mode 100644 dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/KnownTestsResponse.java rename dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/{MetaDto.java => api/dto/response/Meta.java} (68%) rename dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/{ => api/dto/response}/TestIdentifierJson.java (95%) create mode 100644 dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestManagementTestsResponse.java diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApi.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApi.java index 6648f90c5fd..ba174b60ffb 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApi.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApi.java @@ -1,6 +1,7 @@ package datadog.trace.civisibility.config; import datadog.trace.api.civisibility.config.TestFQN; +import datadog.trace.civisibility.config.api.dto.request.TracerEnvironment; import java.io.IOException; import java.util.Collection; import java.util.Collections; diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApiImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApiImpl.java index 9dcc6d65f10..3126401e672 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApiImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApiImpl.java @@ -1,6 +1,5 @@ package datadog.trace.civisibility.config; -import com.squareup.moshi.Json; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; import com.squareup.moshi.Types; @@ -24,6 +23,16 @@ import datadog.trace.api.civisibility.telemetry.tag.RequireGit; import datadog.trace.api.civisibility.telemetry.tag.TestManagementEnabled; import datadog.trace.civisibility.communication.TelemetryListener; +import datadog.trace.civisibility.config.api.dto.Data; +import datadog.trace.civisibility.config.api.dto.Envelope; +import datadog.trace.civisibility.config.api.dto.MultiEnvelope; +import datadog.trace.civisibility.config.api.dto.request.KnownTestsRequest; +import datadog.trace.civisibility.config.api.dto.request.TestManagementRequest; +import datadog.trace.civisibility.config.api.dto.request.TracerEnvironment; +import datadog.trace.civisibility.config.api.dto.response.KnownTestsResponse; +import datadog.trace.civisibility.config.api.dto.response.Meta; +import datadog.trace.civisibility.config.api.dto.response.TestIdentifierJson; +import datadog.trace.civisibility.config.api.dto.response.TestManagementTestsResponse; import datadog.trace.util.RandomUtils; import java.io.IOException; import java.lang.reflect.ParameterizedType; @@ -58,13 +67,14 @@ public class ConfigurationApiImpl implements ConfigurationApi { private final CiVisibilityMetricCollector metricCollector; private final Supplier uuidGenerator; - private final JsonAdapter> requestAdapter; - private final JsonAdapter> settingsResponseAdapter; - private final JsonAdapter> testIdentifiersResponseAdapter; - private final JsonAdapter> knownTestsRequestAdapter; - private final JsonAdapter> testFullNamesResponseAdapter; - private final JsonAdapter> testManagementRequestAdapter; - private final JsonAdapter> testManagementTestsResponseAdapter; + private final JsonAdapter> requestAdapter; + private final JsonAdapter> settingsResponseAdapter; + private final JsonAdapter> testIdentifiersResponseAdapter; + private final JsonAdapter> knownTestsRequestAdapter; + private final JsonAdapter> testFullNamesResponseAdapter; + private final JsonAdapter> testManagementRequestAdapter; + private final JsonAdapter> + testManagementTestsResponseAdapter; public ConfigurationApiImpl(BackendApi backendApi, CiVisibilityMetricCollector metricCollector) { this(backendApi, metricCollector, () -> RandomUtils.randomUUID().toString()); @@ -83,51 +93,44 @@ public ConfigurationApiImpl(BackendApi backendApi, CiVisibilityMetricCollector m .add(ConfigurationsJsonAdapter.INSTANCE) .add(CiVisibilitySettings.JsonAdapter.INSTANCE) .add(EarlyFlakeDetectionSettings.JsonAdapter.INSTANCE) - .add(MetaDto.JsonAdapter.INSTANCE) + .add(Meta.JsonAdapter.INSTANCE) .build(); ParameterizedType requestType = - Types.newParameterizedTypeWithOwner( - ConfigurationApiImpl.class, EnvelopeDto.class, TracerEnvironment.class); + Types.newParameterizedType(Envelope.class, TracerEnvironment.class); requestAdapter = moshi.adapter(requestType); ParameterizedType settingsResponseType = - Types.newParameterizedTypeWithOwner( - ConfigurationApiImpl.class, EnvelopeDto.class, CiVisibilitySettings.class); + Types.newParameterizedType(Envelope.class, CiVisibilitySettings.class); settingsResponseAdapter = moshi.adapter(settingsResponseType); ParameterizedType testIdentifiersResponseType = - Types.newParameterizedTypeWithOwner( - ConfigurationApiImpl.class, MultiEnvelopeDto.class, TestIdentifierJson.class); + Types.newParameterizedType(MultiEnvelope.class, TestIdentifierJson.class); testIdentifiersResponseAdapter = moshi.adapter(testIdentifiersResponseType); ParameterizedType knownTestsRequestType = - Types.newParameterizedTypeWithOwner( - ConfigurationApiImpl.class, EnvelopeDto.class, KnownTestsRequestDto.class); + Types.newParameterizedType(Envelope.class, KnownTestsRequest.class); knownTestsRequestAdapter = moshi.adapter(knownTestsRequestType); ParameterizedType testFullNamesResponseType = - Types.newParameterizedTypeWithOwner( - ConfigurationApiImpl.class, EnvelopeDto.class, KnownTestsDto.class); + Types.newParameterizedType(Envelope.class, KnownTestsResponse.class); testFullNamesResponseAdapter = moshi.adapter(testFullNamesResponseType); ParameterizedType testManagementRequestType = - Types.newParameterizedTypeWithOwner( - ConfigurationApiImpl.class, EnvelopeDto.class, TestManagementDto.class); + Types.newParameterizedType(Envelope.class, TestManagementRequest.class); testManagementRequestAdapter = moshi.adapter(testManagementRequestType); ParameterizedType testManagementTestsResponseType = - Types.newParameterizedTypeWithOwner( - ConfigurationApiImpl.class, EnvelopeDto.class, TestManagementTestsDto.class); + Types.newParameterizedType(Envelope.class, TestManagementTestsResponse.class); testManagementTestsResponseAdapter = moshi.adapter(testManagementTestsResponseType); } @Override public CiVisibilitySettings getSettings(TracerEnvironment tracerEnvironment) throws IOException { String uuid = uuidGenerator.get(); - EnvelopeDto settingsRequest = - new EnvelopeDto<>( - new DataDto<>(uuid, "ci_app_test_service_libraries_settings", tracerEnvironment)); + Envelope settingsRequest = + new Envelope<>( + new Data<>(uuid, "ci_app_test_service_libraries_settings", tracerEnvironment)); String json = requestAdapter.toJson(settingsRequest); RequestBody requestBody = RequestBody.create(JSON, json); @@ -176,11 +179,11 @@ public SkippableTests getSkippableTests(TracerEnvironment tracerEnvironment) thr .build(); String uuid = uuidGenerator.get(); - EnvelopeDto request = - new EnvelopeDto<>(new DataDto<>(uuid, "test_params", tracerEnvironment)); + Envelope request = + new Envelope<>(new Data<>(uuid, "test_params", tracerEnvironment)); String json = requestAdapter.toJson(request); RequestBody requestBody = RequestBody.create(JSON, json); - MultiEnvelopeDto response = + MultiEnvelope response = backendApi.post( SKIPPABLE_TESTS_URI, requestBody, @@ -191,7 +194,7 @@ public SkippableTests getSkippableTests(TracerEnvironment tracerEnvironment) thr Configurations requestConf = tracerEnvironment.getConfigurations(); Map> testIdentifiersByModule = new HashMap<>(); - for (DataDto dataDto : response.data) { + for (Data dataDto : response.data) { TestIdentifierJson testIdentifierJson = dataDto.getAttributes(); Configurations conf = testIdentifierJson.getConfigurations(); String moduleName = @@ -225,12 +228,11 @@ public Map> getFlakyTestsByModule(TracerEnvironment .build(); String uuid = uuidGenerator.get(); - EnvelopeDto request = - new EnvelopeDto<>( - new DataDto<>(uuid, "flaky_test_from_libraries_params", tracerEnvironment)); + Envelope request = + new Envelope<>(new Data<>(uuid, "flaky_test_from_libraries_params", tracerEnvironment)); String json = requestAdapter.toJson(request); RequestBody requestBody = RequestBody.create(JSON, json); - Collection> response = + Collection> response = backendApi.post( FLAKY_TESTS_URI, requestBody, @@ -244,7 +246,7 @@ public Map> getFlakyTestsByModule(TracerEnvironment int flakyTestsCount = 0; Map> testIdentifiers = new HashMap<>(); - for (DataDto dataDto : response) { + for (Data dataDto : response) { TestIdentifierJson testIdentifierJson = dataDto.getAttributes(); Configurations conf = testIdentifierJson.getConfigurations(); String moduleName = @@ -281,12 +283,12 @@ public Map> getKnownTestsByModule(TracerEnvironment LOGGER.debug( "Fetching known tests page #{}{}", pageNumber, pageState != null ? " with cursor" : ""); String uuid = uuidGenerator.get(); - KnownTestsRequestDto requestDto = new KnownTestsRequestDto(tracerEnvironment, pageState); - EnvelopeDto request = - new EnvelopeDto<>(new DataDto<>(uuid, "ci_app_libraries_tests_request", requestDto)); + KnownTestsRequest requestDto = new KnownTestsRequest(tracerEnvironment, pageState); + Envelope request = + new Envelope<>(new Data<>(uuid, "ci_app_libraries_tests_request", requestDto)); String json = knownTestsRequestAdapter.toJson(request); RequestBody requestBody = RequestBody.create(JSON, json); - KnownTestsDto knownTests = + KnownTestsResponse knownTests = backendApi.post( KNOWN_TESTS_URI, requestBody, @@ -395,12 +397,12 @@ public Map>> getTestManagementTests .build(); String uuid = uuidGenerator.get(); - EnvelopeDto request = - new EnvelopeDto<>( - new DataDto<>( + Envelope request = + new Envelope<>( + new Data<>( uuid, "ci_app_libraries_tests_request", - new TestManagementDto( + new TestManagementRequest( tracerEnvironment.getRepositoryUrl(), commitMessage, tracerEnvironment.getConfigurations().getTestBundle(), @@ -408,7 +410,7 @@ public Map>> getTestManagementTests tracerEnvironment.getBranch()))); String json = testManagementRequestAdapter.toJson(request); RequestBody requestBody = RequestBody.create(JSON, json); - TestManagementTestsDto testManagementTestsDto = + TestManagementTestsResponse testManagementTestsResponse = backendApi.post( TEST_MANAGEMENT_TESTS_URI, requestBody, @@ -419,31 +421,31 @@ public Map>> getTestManagementTests telemetryListener, false); - return parseTestManagementTests(testManagementTestsDto); + return parseTestManagementTests(testManagementTestsResponse); } private Map>> parseTestManagementTests( - TestManagementTestsDto testsManagementTestsDto) { + TestManagementTestsResponse testsManagementTestsResponse) { int testManagementTestsCount = 0; Map> quarantinedTestsByModule = new HashMap<>(); Map> disabledTestsByModule = new HashMap<>(); Map> attemptToFixTestsByModule = new HashMap<>(); - for (Map.Entry e : - testsManagementTestsDto.getModules().entrySet()) { + for (Map.Entry e : + testsManagementTestsResponse.getModules().entrySet()) { String moduleName = e.getKey(); - Map testsBySuiteName = e.getValue().getSuites(); + Map testsBySuiteName = e.getValue().getSuites(); - for (Map.Entry se : testsBySuiteName.entrySet()) { + for (Map.Entry se : testsBySuiteName.entrySet()) { String suiteName = se.getKey(); - Map tests = se.getValue().getTests(); + Map tests = se.getValue().getTests(); testManagementTestsCount += tests.size(); - for (Map.Entry te : tests.entrySet()) { + for (Map.Entry te : tests.entrySet()) { String testName = te.getKey(); - TestManagementTestsDto.Properties properties = te.getValue(); + TestManagementTestsResponse.Properties properties = te.getValue(); if (properties.isQuarantined()) { quarantinedTestsByModule .computeIfAbsent(moduleName, k -> new HashSet<>()) @@ -475,128 +477,4 @@ private Map>> parseTestManagementTe return testsByTypeByModule; } - - private static final class EnvelopeDto { - private final DataDto data; - - private EnvelopeDto(DataDto data) { - this.data = data; - } - } - - private static final class MultiEnvelopeDto { - private final Collection> data; - private final @Nullable MetaDto meta; - - private MultiEnvelopeDto(Collection> data, MetaDto meta) { - this.data = data; - this.meta = meta; - } - } - - private static final class DataDto { - // TODO: extract all DTO logic to common utilities - private final String id; - private final String type; - private final T attributes; - - private DataDto(String id, String type, T attributes) { - this.id = id; - this.type = type; - this.attributes = attributes; - } - - public T getAttributes() { - return attributes; - } - } - - private static final class KnownTestsDto { - private final Map>> tests; - - @Json(name = "page_info") - private final PageInfoResponse pageInfo; - - private KnownTestsDto(Map>> tests, PageInfoResponse pageInfo) { - this.tests = tests; - this.pageInfo = pageInfo; - } - - public boolean hasNextPage() { - return pageInfo != null && pageInfo.hasNext; - } - - @Nullable - public Integer getPageSize() { - return pageInfo != null ? pageInfo.size : null; - } - - @Nullable - public String getNextPageCursor() { - return pageInfo != null ? pageInfo.cursor : null; - } - } - - private static final class PageInfoResponse { - private final String cursor; - private final int size; - - @Json(name = "has_next") - private final boolean hasNext; - - private PageInfoResponse(String cursor, int size, boolean hasNext) { - this.cursor = cursor; - this.size = size; - this.hasNext = hasNext; - } - } - - private static final class KnownTestsRequestDto { - @Json(name = "repository_url") - private final String repositoryUrl; - - private final String service; - private final String env; - - @Json(name = "page_info") - private final PageInfoRequest pageInfo; - - private KnownTestsRequestDto(TracerEnvironment tracerEnvironment, @Nullable String pageState) { - this.repositoryUrl = tracerEnvironment.getRepositoryUrl(); - this.service = tracerEnvironment.getService(); - this.env = tracerEnvironment.getEnv(); - this.pageInfo = new PageInfoRequest(pageState); - } - - private static final class PageInfoRequest { - @Json(name = "page_state") - @Nullable - private final String pageState; - - private PageInfoRequest(@Nullable String pageState) { - this.pageState = pageState; - } - } - } - - private static final class TestManagementDto { - @Json(name = "repository_url") - private final String repositoryUrl; - - @Json(name = "commit_message") - private final String commitMessage; - - private final String module; - private final String sha; - private final String branch; - - private TestManagementDto( - String repositoryUrl, String commitMessage, String module, String sha, String branch) { - this.repositoryUrl = repositoryUrl; - this.commitMessage = commitMessage; - this.module = module; - this.sha = sha; - this.branch = branch; - } - } } 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 46ecb133ff3..b5ff526b2c4 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 @@ -7,6 +7,7 @@ import datadog.trace.api.git.GitInfo; import datadog.trace.api.git.GitInfoProvider; import datadog.trace.civisibility.ci.PullRequestInfo; +import datadog.trace.civisibility.config.api.dto.request.TracerEnvironment; import datadog.trace.civisibility.diff.Diff; import datadog.trace.civisibility.diff.LineDiff; import datadog.trace.civisibility.git.tree.GitClient; diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/FileBasedConfigurationApi.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/FileBasedConfigurationApi.java index 701180157bf..f1fe09cc31d 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/FileBasedConfigurationApi.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/FileBasedConfigurationApi.java @@ -2,11 +2,21 @@ import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; +import com.squareup.moshi.Types; import datadog.trace.api.civisibility.config.Configurations; import datadog.trace.api.civisibility.config.TestFQN; import datadog.trace.api.civisibility.config.TestIdentifier; import datadog.trace.api.civisibility.config.TestMetadata; +import datadog.trace.civisibility.config.api.dto.Data; +import datadog.trace.civisibility.config.api.dto.Envelope; +import datadog.trace.civisibility.config.api.dto.MultiEnvelope; +import datadog.trace.civisibility.config.api.dto.request.TracerEnvironment; +import datadog.trace.civisibility.config.api.dto.response.KnownTestsResponse; +import datadog.trace.civisibility.config.api.dto.response.Meta; +import datadog.trace.civisibility.config.api.dto.response.TestIdentifierJson; +import datadog.trace.civisibility.config.api.dto.response.TestManagementTestsResponse; import java.io.IOException; +import java.lang.reflect.ParameterizedType; import java.nio.file.Path; import java.util.BitSet; import java.util.Collection; @@ -36,10 +46,10 @@ public class FileBasedConfigurationApi implements ConfigurationApi { @Nullable private final Path knownTestsPath; @Nullable private final Path testManagementPath; - private final JsonAdapter settingsAdapter; - private final JsonAdapter knownTestsAdapter; - private final JsonAdapter testManagementAdapter; - private final JsonAdapter testIdentifiersAdapter; + private final JsonAdapter> settingsAdapter; + private final JsonAdapter> knownTestsAdapter; + private final JsonAdapter> testManagementAdapter; + private final JsonAdapter> testIdentifiersAdapter; public FileBasedConfigurationApi( @Nullable Path settingsPath, @@ -59,13 +69,24 @@ public FileBasedConfigurationApi( .add(CiVisibilitySettings.JsonAdapter.INSTANCE) .add(EarlyFlakeDetectionSettings.JsonAdapter.INSTANCE) .add(TestManagementSettings.JsonAdapter.INSTANCE) - .add(MetaDto.JsonAdapter.INSTANCE) + .add(Meta.JsonAdapter.INSTANCE) .build(); - settingsAdapter = moshi.adapter(SettingsEnvelope.class); - knownTestsAdapter = moshi.adapter(KnownTestsEnvelope.class); - testManagementAdapter = moshi.adapter(TestManagementEnvelope.class); - testIdentifiersAdapter = moshi.adapter(TestIdentifiersEnvelope.class); + ParameterizedType settingsType = + Types.newParameterizedType(Envelope.class, CiVisibilitySettings.class); + settingsAdapter = moshi.adapter(settingsType); + + ParameterizedType knownTestsType = + Types.newParameterizedType(Envelope.class, KnownTestsResponse.class); + knownTestsAdapter = moshi.adapter(knownTestsType); + + ParameterizedType testManagementType = + Types.newParameterizedType(Envelope.class, TestManagementTestsResponse.class); + testManagementAdapter = moshi.adapter(testManagementType); + + ParameterizedType testIdentifiersType = + Types.newParameterizedType(MultiEnvelope.class, TestIdentifierJson.class); + testIdentifiersAdapter = moshi.adapter(testIdentifiersType); } @Override @@ -77,7 +98,7 @@ public CiVisibilitySettings getSettings(TracerEnvironment tracerEnvironment) thr LOGGER.debug("Reading settings from {}", settingsPath); try (BufferedSource source = Okio.buffer(Okio.source(settingsPath))) { - SettingsEnvelope envelope = settingsAdapter.fromJson(source); + Envelope envelope = settingsAdapter.fromJson(source); if (envelope != null && envelope.data != null && envelope.data.attributes != null) { return envelope.data.attributes; } @@ -94,7 +115,7 @@ public SkippableTests getSkippableTests(TracerEnvironment tracerEnvironment) thr LOGGER.debug("Reading skippable tests from {}", skippableTestsPath); try (BufferedSource source = Okio.buffer(Okio.source(skippableTestsPath))) { - TestIdentifiersEnvelope envelope = testIdentifiersAdapter.fromJson(source); + MultiEnvelope envelope = testIdentifiersAdapter.fromJson(source); if (envelope != null && envelope.data != null) { return toSkippableTests(envelope, tracerEnvironment); } @@ -103,11 +124,11 @@ public SkippableTests getSkippableTests(TracerEnvironment tracerEnvironment) thr } private SkippableTests toSkippableTests( - TestIdentifiersEnvelope envelope, TracerEnvironment tracerEnvironment) { + MultiEnvelope envelope, TracerEnvironment tracerEnvironment) { Configurations requestConf = tracerEnvironment.getConfigurations(); Map> identifiersByModule = new HashMap<>(); - for (TestIdentifierDataDto dataDto : envelope.data) { + for (Data dataDto : envelope.data) { TestIdentifierJson testIdJson = dataDto.attributes; if (testIdJson == null) { continue; @@ -138,7 +159,7 @@ public Map> getFlakyTestsByModule(TracerEnvironment LOGGER.debug("Reading flaky tests from {}", flakyTestsPath); try (BufferedSource source = Okio.buffer(Okio.source(flakyTestsPath))) { - TestIdentifiersEnvelope envelope = testIdentifiersAdapter.fromJson(source); + MultiEnvelope envelope = testIdentifiersAdapter.fromJson(source); if (envelope != null && envelope.data != null) { return toFlakyTestsByModule(envelope, tracerEnvironment); } @@ -147,11 +168,11 @@ public Map> getFlakyTestsByModule(TracerEnvironment } private Map> toFlakyTestsByModule( - TestIdentifiersEnvelope envelope, TracerEnvironment tracerEnvironment) { + MultiEnvelope envelope, TracerEnvironment tracerEnvironment) { Configurations requestConf = tracerEnvironment.getConfigurations(); Map> result = new HashMap<>(); - for (TestIdentifierDataDto dataDto : envelope.data) { + for (Data dataDto : envelope.data) { TestIdentifierJson testIdJson = dataDto.attributes; if (testIdJson == null) { continue; @@ -179,7 +200,7 @@ public Map> getKnownTestsByModule(TracerEnvironment LOGGER.debug("Reading known tests from {}", knownTestsPath); try (BufferedSource source = Okio.buffer(Okio.source(knownTestsPath))) { - KnownTestsEnvelope envelope = knownTestsAdapter.fromJson(source); + Envelope envelope = knownTestsAdapter.fromJson(source); if (envelope != null && envelope.data != null && envelope.data.attributes != null @@ -221,7 +242,7 @@ public Map>> getTestManagementTests LOGGER.debug("Reading test management data from {}", testManagementPath); try (BufferedSource source = Okio.buffer(Okio.source(testManagementPath))) { - TestManagementEnvelope envelope = testManagementAdapter.fromJson(source); + Envelope envelope = testManagementAdapter.fromJson(source); if (envelope != null && envelope.data != null && envelope.data.attributes != null) { return parseTestManagementTests(envelope.data.attributes); } @@ -230,23 +251,25 @@ public Map>> getTestManagementTests } private Map>> parseTestManagementTests( - TestManagementTestsDto dto) { + TestManagementTestsResponse dto) { Map> quarantined = new HashMap<>(); Map> disabled = new HashMap<>(); Map> attemptToFix = new HashMap<>(); - for (Map.Entry moduleEntry : + for (Map.Entry moduleEntry : dto.getModules().entrySet()) { String moduleName = moduleEntry.getKey(); - Map suites = moduleEntry.getValue().getSuites(); + Map suites = moduleEntry.getValue().getSuites(); - for (Map.Entry suiteEntry : suites.entrySet()) { + for (Map.Entry suiteEntry : suites.entrySet()) { String suiteName = suiteEntry.getKey(); - Map tests = suiteEntry.getValue().getTests(); + Map tests = + suiteEntry.getValue().getTests(); - for (Map.Entry testEntry : tests.entrySet()) { + for (Map.Entry testEntry : + tests.entrySet()) { String testName = testEntry.getKey(); - TestManagementTestsDto.Properties props = testEntry.getValue(); + TestManagementTestsResponse.Properties props = testEntry.getValue(); TestFQN fqn = new TestFQN(suiteName, testName); if (props.isQuarantined()) { @@ -268,41 +291,4 @@ private Map>> parseTestManagementTe result.put(TestSetting.ATTEMPT_TO_FIX, attemptToFix); return result; } - - // Settings envelope: { "data": { "attributes": CiVisibilitySettings } } - static final class SettingsEnvelope { - DataDto data; - } - - // Known tests envelope: { "data": { "attributes": { "tests": ... } } } - static final class KnownTestsEnvelope { - DataDto data; - } - - // Test management envelope: { "data": { "attributes": TestManagementTestsDto } } - static final class TestManagementEnvelope { - DataDto data; - } - - static final class DataDto { - String id; - String type; - T attributes; - } - - static final class KnownTestsAttributes { - Map>> tests; - } - - // Skippable/flaky tests envelope: { "data": [...], "meta": { ... } } - static final class TestIdentifiersEnvelope { - Collection data; - @Nullable MetaDto meta; - } - - static final class TestIdentifierDataDto { - String id; - String type; - TestIdentifierJson attributes; - } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TestManagementTestsDto.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TestManagementTestsDto.java deleted file mode 100644 index 54293b436b4..00000000000 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TestManagementTestsDto.java +++ /dev/null @@ -1,48 +0,0 @@ -package datadog.trace.civisibility.config; - -import java.util.Collections; -import java.util.Map; -import javax.annotation.Nullable; - -final class TestManagementTestsDto { - - @Nullable Map modules; - - Map getModules() { - return modules != null ? modules : Collections.emptyMap(); - } - - static final class Properties { - @Nullable Map properties; - - boolean isQuarantined() { - return properties != null - && properties.getOrDefault(TestSetting.QUARANTINED.asString(), false); - } - - boolean isDisabled() { - return properties != null && properties.getOrDefault(TestSetting.DISABLED.asString(), false); - } - - boolean isAttemptToFix() { - return properties != null - && properties.getOrDefault(TestSetting.ATTEMPT_TO_FIX.asString(), false); - } - } - - static final class Tests { - @Nullable Map tests; - - Map getTests() { - return tests != null ? tests : Collections.emptyMap(); - } - } - - static final class Suites { - @Nullable Map suites; - - Map getSuites() { - return suites != null ? suites : Collections.emptyMap(); - } - } -} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/Data.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/Data.java new file mode 100644 index 00000000000..4dddb242654 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/Data.java @@ -0,0 +1,17 @@ +package datadog.trace.civisibility.config.api.dto; + +public final class Data { + public final String id; + public final String type; + public final T attributes; + + public Data(String id, String type, T attributes) { + this.id = id; + this.type = type; + this.attributes = attributes; + } + + public T getAttributes() { + return attributes; + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/Envelope.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/Envelope.java new file mode 100644 index 00000000000..956dc61df2f --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/Envelope.java @@ -0,0 +1,9 @@ +package datadog.trace.civisibility.config.api.dto; + +public final class Envelope { + public final Data data; + + public Envelope(Data data) { + this.data = data; + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/MultiEnvelope.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/MultiEnvelope.java new file mode 100644 index 00000000000..2138be6960a --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/MultiEnvelope.java @@ -0,0 +1,15 @@ +package datadog.trace.civisibility.config.api.dto; + +import datadog.trace.civisibility.config.api.dto.response.Meta; +import java.util.Collection; +import javax.annotation.Nullable; + +public final class MultiEnvelope { + public final Collection> data; + @Nullable public final Meta meta; + + public MultiEnvelope(Collection> data, @Nullable Meta meta) { + this.data = data; + this.meta = meta; + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/PageInfo.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/PageInfo.java new file mode 100644 index 00000000000..045190cf7fb --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/PageInfo.java @@ -0,0 +1,33 @@ +package datadog.trace.civisibility.config.api.dto; + +import com.squareup.moshi.Json; +import javax.annotation.Nullable; + +public final class PageInfo { + + private PageInfo() {} + + public static final class Request { + @Json(name = "page_state") + @Nullable + public final String pageState; + + public Request(@Nullable String pageState) { + this.pageState = pageState; + } + } + + public static final class Response { + public final String cursor; + public final int size; + + @Json(name = "has_next") + public final boolean hasNext; + + public Response(String cursor, int size, boolean hasNext) { + this.cursor = cursor; + this.size = size; + this.hasNext = hasNext; + } + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/request/KnownTestsRequest.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/request/KnownTestsRequest.java new file mode 100644 index 00000000000..df86c7c76f9 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/request/KnownTestsRequest.java @@ -0,0 +1,23 @@ +package datadog.trace.civisibility.config.api.dto.request; + +import com.squareup.moshi.Json; +import datadog.trace.civisibility.config.api.dto.PageInfo; +import javax.annotation.Nullable; + +public final class KnownTestsRequest { + @Json(name = "repository_url") + public final String repositoryUrl; + + public final String service; + public final String env; + + @Json(name = "page_info") + public final PageInfo.Request pageInfo; + + public KnownTestsRequest(TracerEnvironment tracerEnvironment, @Nullable String pageState) { + this.repositoryUrl = tracerEnvironment.getRepositoryUrl(); + this.service = tracerEnvironment.getService(); + this.env = tracerEnvironment.getEnv(); + this.pageInfo = new PageInfo.Request(pageState); + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/request/TestManagementRequest.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/request/TestManagementRequest.java new file mode 100644 index 00000000000..b6d411d66ab --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/request/TestManagementRequest.java @@ -0,0 +1,24 @@ +package datadog.trace.civisibility.config.api.dto.request; + +import com.squareup.moshi.Json; + +public final class TestManagementRequest { + @Json(name = "repository_url") + public final String repositoryUrl; + + @Json(name = "commit_message") + public final String commitMessage; + + public final String module; + public final String sha; + public final String branch; + + public TestManagementRequest( + String repositoryUrl, String commitMessage, String module, String sha, String branch) { + this.repositoryUrl = repositoryUrl; + this.commitMessage = commitMessage; + this.module = module; + this.sha = sha; + this.branch = branch; + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TracerEnvironment.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/request/TracerEnvironment.java similarity index 98% rename from dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TracerEnvironment.java rename to dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/request/TracerEnvironment.java index c06210123a9..b609279c909 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TracerEnvironment.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/request/TracerEnvironment.java @@ -1,4 +1,4 @@ -package datadog.trace.civisibility.config; +package datadog.trace.civisibility.config.api.dto.request; import com.squareup.moshi.Json; import datadog.trace.api.civisibility.config.Configurations; diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/KnownTestsResponse.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/KnownTestsResponse.java new file mode 100644 index 00000000000..39d4dd510ed --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/KnownTestsResponse.java @@ -0,0 +1,34 @@ +package datadog.trace.civisibility.config.api.dto.response; + +import com.squareup.moshi.Json; +import datadog.trace.civisibility.config.api.dto.PageInfo; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + +public final class KnownTestsResponse { + public final Map>> tests; + + @Json(name = "page_info") + public final PageInfo.Response pageInfo; + + public KnownTestsResponse( + Map>> tests, PageInfo.Response pageInfo) { + this.tests = tests; + this.pageInfo = pageInfo; + } + + public boolean hasNextPage() { + return pageInfo != null && pageInfo.hasNext; + } + + @Nullable + public Integer getPageSize() { + return pageInfo != null ? pageInfo.size : null; + } + + @Nullable + public String getNextPageCursor() { + return pageInfo != null ? pageInfo.cursor : null; + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/MetaDto.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/Meta.java similarity index 68% rename from dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/MetaDto.java rename to dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/Meta.java index ace87a71941..db52634ebb7 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/MetaDto.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/Meta.java @@ -1,4 +1,4 @@ -package datadog.trace.civisibility.config; +package datadog.trace.civisibility.config.api.dto.response; import com.squareup.moshi.FromJson; import com.squareup.moshi.ToJson; @@ -9,23 +9,23 @@ import java.util.Map; import javax.annotation.Nullable; -final class MetaDto { +public final class Meta { - @Nullable final String correlationId; - @Nullable final Map coverage; + @Nullable public final String correlationId; + @Nullable public final Map coverage; - MetaDto(@Nullable String correlationId, @Nullable Map coverage) { + public Meta(@Nullable String correlationId, @Nullable Map coverage) { this.correlationId = correlationId; this.coverage = coverage; } - static final class JsonAdapter { + public static final class JsonAdapter { - static final JsonAdapter INSTANCE = new JsonAdapter(); + public static final JsonAdapter INSTANCE = new JsonAdapter(); @SuppressWarnings("unchecked") @FromJson - public MetaDto fromJson(Map json) { + public Meta fromJson(Map json) { if (json == null) { return null; } @@ -47,11 +47,11 @@ public MetaDto fromJson(Map json) { coverage = null; } - return new MetaDto((String) json.get("correlation_id"), coverage); + return new Meta((String) json.get("correlation_id"), coverage); } @ToJson - public Map toJson(MetaDto metaDto) { + public Map toJson(Meta meta) { throw new UnsupportedOperationException(); } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TestIdentifierJson.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestIdentifierJson.java similarity index 95% rename from dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TestIdentifierJson.java rename to dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestIdentifierJson.java index a6a7b8f932b..9afa856675d 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/TestIdentifierJson.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestIdentifierJson.java @@ -1,4 +1,4 @@ -package datadog.trace.civisibility.config; +package datadog.trace.civisibility.config.api.dto.response; import com.squareup.moshi.Json; import datadog.trace.api.civisibility.config.Configurations; diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestManagementTestsResponse.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestManagementTestsResponse.java new file mode 100644 index 00000000000..aeb6bb08a1a --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestManagementTestsResponse.java @@ -0,0 +1,49 @@ +package datadog.trace.civisibility.config.api.dto.response; + +import datadog.trace.civisibility.config.TestSetting; +import java.util.Collections; +import java.util.Map; +import javax.annotation.Nullable; + +public final class TestManagementTestsResponse { + + @Nullable public Map modules; + + public Map getModules() { + return modules != null ? modules : Collections.emptyMap(); + } + + public static final class Properties { + @Nullable public Map properties; + + public boolean isQuarantined() { + return properties != null + && properties.getOrDefault(TestSetting.QUARANTINED.asString(), false); + } + + public boolean isDisabled() { + return properties != null && properties.getOrDefault(TestSetting.DISABLED.asString(), false); + } + + public boolean isAttemptToFix() { + return properties != null + && properties.getOrDefault(TestSetting.ATTEMPT_TO_FIX.asString(), false); + } + } + + public static final class Tests { + @Nullable public Map tests; + + public Map getTests() { + return tests != null ? tests : Collections.emptyMap(); + } + } + + public static final class Suites { + @Nullable public Map suites; + + public Map getSuites() { + return suites != null ? suites : Collections.emptyMap(); + } + } +} 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 d37abda8f52..a0f47207ade 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 @@ -11,6 +11,7 @@ import datadog.trace.api.civisibility.config.TestIdentifier import datadog.trace.api.civisibility.config.TestMetadata import datadog.trace.api.civisibility.telemetry.CiVisibilityMetricCollector import datadog.trace.api.intake.Intake +import datadog.trace.civisibility.config.api.dto.request.TracerEnvironment import freemarker.core.Environment import freemarker.core.InvalidReferenceException import freemarker.template.Template diff --git a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/TracerEnvironmentTest.groovy b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/TracerEnvironmentTest.groovy index 8145bf4adbb..43bba19e6f0 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/TracerEnvironmentTest.groovy +++ b/dd-java-agent/agent-ci-visibility/src/test/groovy/datadog/trace/civisibility/config/TracerEnvironmentTest.groovy @@ -1,5 +1,6 @@ package datadog.trace.civisibility.config +import datadog.trace.civisibility.config.api.dto.request.TracerEnvironment import spock.lang.Specification class TracerEnvironmentTest extends Specification { diff --git a/dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/config/FileBasedConfigurationApiTest.java b/dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/config/FileBasedConfigurationApiTest.java index 81d492590df..e96c0c69bbf 100644 --- a/dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/config/FileBasedConfigurationApiTest.java +++ b/dd-java-agent/agent-ci-visibility/src/test/java/datadog/trace/civisibility/config/FileBasedConfigurationApiTest.java @@ -9,6 +9,7 @@ import datadog.trace.api.civisibility.config.TestFQN; import datadog.trace.api.civisibility.config.TestIdentifier; import datadog.trace.api.civisibility.config.TestMetadata; +import datadog.trace.civisibility.config.api.dto.request.TracerEnvironment; import freemarker.template.Configuration; import freemarker.template.Template; import java.io.IOException; From b3484085ea4031eaf9a2836db3e10a76b5a35f30 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Thu, 28 May 2026 11:30:42 +0200 Subject: [PATCH 2/2] feat: move common extraction logic to DTOs --- .../config/ConfigurationApiImpl.java | 210 +++-------------- .../config/FileBasedConfigurationApi.java | 222 ++++-------------- .../civisibility/config/SkippableTests.java | 31 +++ .../config/api/dto/ConfigurationApiMoshi.java | 28 +++ .../civisibility/config/api/dto/Data.java | 4 - .../api/dto/response/KnownTestsResponse.java | 30 +++ .../api/dto/response/TestIdentifierJson.java | 35 +++ .../response/TestManagementTestsResponse.java | 60 +++++ 8 files changed, 270 insertions(+), 350 deletions(-) create mode 100644 dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/ConfigurationApiMoshi.java diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApiImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApiImpl.java index 3126401e672..49665328c12 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApiImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/ConfigurationApiImpl.java @@ -5,10 +5,7 @@ import com.squareup.moshi.Types; import datadog.communication.BackendApi; import datadog.communication.http.OkHttpUtils; -import datadog.trace.api.civisibility.config.Configurations; import datadog.trace.api.civisibility.config.TestFQN; -import datadog.trace.api.civisibility.config.TestIdentifier; -import datadog.trace.api.civisibility.config.TestMetadata; import datadog.trace.api.civisibility.telemetry.CiVisibilityCountMetric; import datadog.trace.api.civisibility.telemetry.CiVisibilityDistributionMetric; import datadog.trace.api.civisibility.telemetry.CiVisibilityMetricCollector; @@ -23,6 +20,7 @@ import datadog.trace.api.civisibility.telemetry.tag.RequireGit; import datadog.trace.api.civisibility.telemetry.tag.TestManagementEnabled; import datadog.trace.civisibility.communication.TelemetryListener; +import datadog.trace.civisibility.config.api.dto.ConfigurationApiMoshi; import datadog.trace.civisibility.config.api.dto.Data; import datadog.trace.civisibility.config.api.dto.Envelope; import datadog.trace.civisibility.config.api.dto.MultiEnvelope; @@ -30,17 +28,13 @@ import datadog.trace.civisibility.config.api.dto.request.TestManagementRequest; import datadog.trace.civisibility.config.api.dto.request.TracerEnvironment; import datadog.trace.civisibility.config.api.dto.response.KnownTestsResponse; -import datadog.trace.civisibility.config.api.dto.response.Meta; import datadog.trace.civisibility.config.api.dto.response.TestIdentifierJson; import datadog.trace.civisibility.config.api.dto.response.TestManagementTestsResponse; import datadog.trace.util.RandomUtils; import java.io.IOException; import java.lang.reflect.ParameterizedType; -import java.util.BitSet; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.function.Supplier; @@ -88,41 +82,23 @@ public ConfigurationApiImpl(BackendApi backendApi, CiVisibilityMetricCollector m this.metricCollector = metricCollector; this.uuidGenerator = uuidGenerator; - Moshi moshi = - new Moshi.Builder() - .add(ConfigurationsJsonAdapter.INSTANCE) - .add(CiVisibilitySettings.JsonAdapter.INSTANCE) - .add(EarlyFlakeDetectionSettings.JsonAdapter.INSTANCE) - .add(Meta.JsonAdapter.INSTANCE) - .build(); - - ParameterizedType requestType = - Types.newParameterizedType(Envelope.class, TracerEnvironment.class); - requestAdapter = moshi.adapter(requestType); - - ParameterizedType settingsResponseType = - Types.newParameterizedType(Envelope.class, CiVisibilitySettings.class); - settingsResponseAdapter = moshi.adapter(settingsResponseType); - - ParameterizedType testIdentifiersResponseType = - Types.newParameterizedType(MultiEnvelope.class, TestIdentifierJson.class); - testIdentifiersResponseAdapter = moshi.adapter(testIdentifiersResponseType); - - ParameterizedType knownTestsRequestType = - Types.newParameterizedType(Envelope.class, KnownTestsRequest.class); - knownTestsRequestAdapter = moshi.adapter(knownTestsRequestType); - - ParameterizedType testFullNamesResponseType = - Types.newParameterizedType(Envelope.class, KnownTestsResponse.class); - testFullNamesResponseAdapter = moshi.adapter(testFullNamesResponseType); + Moshi moshi = ConfigurationApiMoshi.create(); + requestAdapter = moshi.adapter(envelopeOf(TracerEnvironment.class)); + settingsResponseAdapter = moshi.adapter(envelopeOf(CiVisibilitySettings.class)); + testIdentifiersResponseAdapter = moshi.adapter(multiEnvelopeOf(TestIdentifierJson.class)); + knownTestsRequestAdapter = moshi.adapter(envelopeOf(KnownTestsRequest.class)); + testFullNamesResponseAdapter = moshi.adapter(envelopeOf(KnownTestsResponse.class)); + testManagementRequestAdapter = moshi.adapter(envelopeOf(TestManagementRequest.class)); + testManagementTestsResponseAdapter = + moshi.adapter(envelopeOf(TestManagementTestsResponse.class)); + } - ParameterizedType testManagementRequestType = - Types.newParameterizedType(Envelope.class, TestManagementRequest.class); - testManagementRequestAdapter = moshi.adapter(testManagementRequestType); + private static ParameterizedType envelopeOf(Class attributesType) { + return Types.newParameterizedType(Envelope.class, attributesType); + } - ParameterizedType testManagementTestsResponseType = - Types.newParameterizedType(Envelope.class, TestManagementTestsResponse.class); - testManagementTestsResponseAdapter = moshi.adapter(testManagementTestsResponseType); + private static ParameterizedType multiEnvelopeOf(Class attributesType) { + return Types.newParameterizedType(MultiEnvelope.class, attributesType); } @Override @@ -191,29 +167,10 @@ public SkippableTests getSkippableTests(TracerEnvironment tracerEnvironment) thr telemetryListener, false); - Configurations requestConf = tracerEnvironment.getConfigurations(); - - Map> testIdentifiersByModule = new HashMap<>(); - for (Data dataDto : response.data) { - TestIdentifierJson testIdentifierJson = dataDto.getAttributes(); - Configurations conf = testIdentifierJson.getConfigurations(); - String moduleName = - (conf != null && conf.getTestBundle() != null ? conf : requestConf).getTestBundle(); - testIdentifiersByModule - .computeIfAbsent(moduleName, k -> new HashMap<>()) - .put(testIdentifierJson.toTestIdentifier(), testIdentifierJson.toTestMetadata()); - } - metricCollector.add( CiVisibilityCountMetric.ITR_SKIPPABLE_TESTS_RESPONSE_TESTS, response.data.size()); - String correlationId = response.meta != null ? response.meta.correlationId : null; - Map coveredLinesByRelativeSourcePath = - response.meta != null && response.meta.coverage != null - ? response.meta.coverage - : Collections.emptyMap(); - return new SkippableTests( - correlationId, testIdentifiersByModule, coveredLinesByRelativeSourcePath); + return SkippableTests.from(response, tracerEnvironment); } @Override @@ -242,23 +199,11 @@ public Map> getFlakyTestsByModule(TracerEnvironment LOGGER.debug("Received {} flaky tests in total", response.size()); - Configurations requestConf = tracerEnvironment.getConfigurations(); - - int flakyTestsCount = 0; - Map> testIdentifiers = new HashMap<>(); - for (Data dataDto : response) { - TestIdentifierJson testIdentifierJson = dataDto.getAttributes(); - Configurations conf = testIdentifierJson.getConfigurations(); - String moduleName = - (conf != null && conf.getTestBundle() != null ? conf : requestConf).getTestBundle(); - testIdentifiers - .computeIfAbsent(moduleName, k -> new HashSet<>()) - .add(testIdentifierJson.toTestIdentifier().toFQN()); - flakyTestsCount++; - } - + Map> testsByModule = + TestIdentifierJson.toTestFQNsByModule(response, tracerEnvironment); + int flakyTestsCount = testsByModule.values().stream().mapToInt(Collection::size).sum(); metricCollector.add(CiVisibilityDistributionMetric.FLAKY_TESTS_RESPONSE_TESTS, flakyTestsCount); - return testIdentifiers; + return testsByModule; } @Nullable @@ -299,7 +244,6 @@ public Map> getKnownTestsByModule(TracerEnvironment telemetryListener, false); - // Merge page's tests into aggregate mergeKnownTests(aggregateTests, knownTests.tests); Integer pageSize = knownTests.getPageSize(); @@ -309,17 +253,22 @@ public Map> getKnownTestsByModule(TracerEnvironment LOGGER.debug("Received page #{} for known tests", pageNumber); } - // Get cursor for next page (if any) - if (knownTests.hasNextPage()) { - pageState = knownTests.getNextPageCursor(); - } else { - pageState = null; - } + pageState = knownTests.hasNextPage() ? knownTests.getNextPageCursor() : null; } while (pageState != null); LOGGER.debug("Finished fetching known tests after {} page(s)", pageNumber); - return parseTestIdentifiers(aggregateTests); + Map> testsByModule = + KnownTestsResponse.toTestFQNsByModule(aggregateTests); + int knownTestsCount = + testsByModule != null + ? testsByModule.values().stream().mapToInt(Collection::size).sum() + : 0; + LOGGER.debug("Received {} known tests in total", knownTestsCount); + metricCollector.add(CiVisibilityDistributionMetric.KNOWN_TESTS_RESPONSE_TESTS, knownTestsCount); + // returning null disables features that rely on known tests; this is intentional on the very + // first execution for a repository, when we want to seed the backend with the initial set. + return testsByModule; } private void mergeKnownTests( @@ -350,40 +299,6 @@ private void mergeKnownTests( } } - private Map> parseTestIdentifiers( - Map>> testsMap) { - int knownTestsCount = 0; - - Map> testIdentifiers = new HashMap<>(); - for (Map.Entry>> e : testsMap.entrySet()) { - String moduleName = e.getKey(); - Map> testsBySuiteName = e.getValue(); - - for (Map.Entry> se : testsBySuiteName.entrySet()) { - String suiteName = se.getKey(); - List testNames = se.getValue(); - knownTestsCount += testNames.size(); - - for (String testName : testNames) { - testIdentifiers - .computeIfAbsent(moduleName, k -> new HashSet<>()) - .add(new TestFQN(suiteName, testName)); - } - } - } - - LOGGER.debug("Received {} known tests in total", knownTestsCount); - metricCollector.add(CiVisibilityDistributionMetric.KNOWN_TESTS_RESPONSE_TESTS, knownTestsCount); - return knownTestsCount > 0 - ? testIdentifiers - // returning null if there are no known tests: - // this will disable the features that are reliant on known tests - // and is done on purpose: - // if no tests are known, this is likely the first execution for this repository, - // and we want to fill the backend with the initial set of tests - : null; - } - @Override public Map>> getTestManagementTestsByModule( TracerEnvironment tracerEnvironment, String commitSha, String commitMessage) @@ -410,7 +325,7 @@ public Map>> getTestManagementTests tracerEnvironment.getBranch()))); String json = testManagementRequestAdapter.toJson(request); RequestBody requestBody = RequestBody.create(JSON, json); - TestManagementTestsResponse testManagementTestsResponse = + TestManagementTestsResponse response = backendApi.post( TEST_MANAGEMENT_TESTS_URI, requestBody, @@ -421,60 +336,11 @@ public Map>> getTestManagementTests telemetryListener, false); - return parseTestManagementTests(testManagementTestsResponse); - } - - private Map>> parseTestManagementTests( - TestManagementTestsResponse testsManagementTestsResponse) { - int testManagementTestsCount = 0; - - Map> quarantinedTestsByModule = new HashMap<>(); - Map> disabledTestsByModule = new HashMap<>(); - Map> attemptToFixTestsByModule = new HashMap<>(); - - for (Map.Entry e : - testsManagementTestsResponse.getModules().entrySet()) { - String moduleName = e.getKey(); - Map testsBySuiteName = e.getValue().getSuites(); - - for (Map.Entry se : testsBySuiteName.entrySet()) { - String suiteName = se.getKey(); - Map tests = se.getValue().getTests(); - - testManagementTestsCount += tests.size(); - - for (Map.Entry te : tests.entrySet()) { - String testName = te.getKey(); - TestManagementTestsResponse.Properties properties = te.getValue(); - if (properties.isQuarantined()) { - quarantinedTestsByModule - .computeIfAbsent(moduleName, k -> new HashSet<>()) - .add(new TestFQN(suiteName, testName)); - } - if (properties.isDisabled()) { - disabledTestsByModule - .computeIfAbsent(moduleName, k -> new HashSet<>()) - .add(new TestFQN(suiteName, testName)); - } - if (properties.isAttemptToFix()) { - attemptToFixTestsByModule - .computeIfAbsent(moduleName, k -> new HashSet<>()) - .add(new TestFQN(suiteName, testName)); - } - } - } - } - - Map>> testsByTypeByModule = new HashMap<>(); - testsByTypeByModule.put(TestSetting.QUARANTINED, quarantinedTestsByModule); - testsByTypeByModule.put(TestSetting.DISABLED, disabledTestsByModule); - testsByTypeByModule.put(TestSetting.ATTEMPT_TO_FIX, attemptToFixTestsByModule); - - LOGGER.debug("Received {} test management tests in total", testManagementTestsCount); + int testsCount = response.totalTestsCount(); + LOGGER.debug("Received {} test management tests in total", testsCount); metricCollector.add( - CiVisibilityDistributionMetric.TEST_MANAGEMENT_TESTS_RESPONSE_TESTS, - testManagementTestsCount); + CiVisibilityDistributionMetric.TEST_MANAGEMENT_TESTS_RESPONSE_TESTS, testsCount); - return testsByTypeByModule; + return response.toTestFQNsBySetting(); } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/FileBasedConfigurationApi.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/FileBasedConfigurationApi.java index f1fe09cc31d..9e28ea35c35 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/FileBasedConfigurationApi.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/FileBasedConfigurationApi.java @@ -3,27 +3,19 @@ import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; import com.squareup.moshi.Types; -import datadog.trace.api.civisibility.config.Configurations; import datadog.trace.api.civisibility.config.TestFQN; -import datadog.trace.api.civisibility.config.TestIdentifier; -import datadog.trace.api.civisibility.config.TestMetadata; -import datadog.trace.civisibility.config.api.dto.Data; +import datadog.trace.civisibility.config.api.dto.ConfigurationApiMoshi; import datadog.trace.civisibility.config.api.dto.Envelope; import datadog.trace.civisibility.config.api.dto.MultiEnvelope; import datadog.trace.civisibility.config.api.dto.request.TracerEnvironment; import datadog.trace.civisibility.config.api.dto.response.KnownTestsResponse; -import datadog.trace.civisibility.config.api.dto.response.Meta; import datadog.trace.civisibility.config.api.dto.response.TestIdentifierJson; import datadog.trace.civisibility.config.api.dto.response.TestManagementTestsResponse; import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.nio.file.Path; -import java.util.BitSet; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; import java.util.Map; import javax.annotation.Nullable; import okio.BufferedSource; @@ -63,30 +55,19 @@ public FileBasedConfigurationApi( this.knownTestsPath = knownTestsPath; this.testManagementPath = testManagementPath; - Moshi moshi = - new Moshi.Builder() - .add(ConfigurationsJsonAdapter.INSTANCE) - .add(CiVisibilitySettings.JsonAdapter.INSTANCE) - .add(EarlyFlakeDetectionSettings.JsonAdapter.INSTANCE) - .add(TestManagementSettings.JsonAdapter.INSTANCE) - .add(Meta.JsonAdapter.INSTANCE) - .build(); - - ParameterizedType settingsType = - Types.newParameterizedType(Envelope.class, CiVisibilitySettings.class); - settingsAdapter = moshi.adapter(settingsType); - - ParameterizedType knownTestsType = - Types.newParameterizedType(Envelope.class, KnownTestsResponse.class); - knownTestsAdapter = moshi.adapter(knownTestsType); + Moshi moshi = ConfigurationApiMoshi.create(); + settingsAdapter = moshi.adapter(envelopeOf(CiVisibilitySettings.class)); + knownTestsAdapter = moshi.adapter(envelopeOf(KnownTestsResponse.class)); + testManagementAdapter = moshi.adapter(envelopeOf(TestManagementTestsResponse.class)); + testIdentifiersAdapter = moshi.adapter(multiEnvelopeOf(TestIdentifierJson.class)); + } - ParameterizedType testManagementType = - Types.newParameterizedType(Envelope.class, TestManagementTestsResponse.class); - testManagementAdapter = moshi.adapter(testManagementType); + private static ParameterizedType envelopeOf(Class attributesType) { + return Types.newParameterizedType(Envelope.class, attributesType); + } - ParameterizedType testIdentifiersType = - Types.newParameterizedType(MultiEnvelope.class, TestIdentifierJson.class); - testIdentifiersAdapter = moshi.adapter(testIdentifiersType); + private static ParameterizedType multiEnvelopeOf(Class attributesType) { + return Types.newParameterizedType(MultiEnvelope.class, attributesType); } @Override @@ -95,15 +76,11 @@ public CiVisibilitySettings getSettings(TracerEnvironment tracerEnvironment) thr LOGGER.debug("Settings file path not provided, returning defaults"); return CiVisibilitySettings.DEFAULT; } - LOGGER.debug("Reading settings from {}", settingsPath); - try (BufferedSource source = Okio.buffer(Okio.source(settingsPath))) { - Envelope envelope = settingsAdapter.fromJson(source); - if (envelope != null && envelope.data != null && envelope.data.attributes != null) { - return envelope.data.attributes; - } - } - return CiVisibilitySettings.DEFAULT; + Envelope envelope = readEnvelope(settingsPath, settingsAdapter); + return envelope != null && envelope.data != null && envelope.data.attributes != null + ? envelope.data.attributes + : CiVisibilitySettings.DEFAULT; } @Override @@ -112,41 +89,13 @@ public SkippableTests getSkippableTests(TracerEnvironment tracerEnvironment) thr LOGGER.debug("Skippable tests file path not provided, returning empty"); return SkippableTests.EMPTY; } - LOGGER.debug("Reading skippable tests from {}", skippableTestsPath); - try (BufferedSource source = Okio.buffer(Okio.source(skippableTestsPath))) { - MultiEnvelope envelope = testIdentifiersAdapter.fromJson(source); - if (envelope != null && envelope.data != null) { - return toSkippableTests(envelope, tracerEnvironment); - } - } - return SkippableTests.EMPTY; - } - - private SkippableTests toSkippableTests( - MultiEnvelope envelope, TracerEnvironment tracerEnvironment) { - Configurations requestConf = tracerEnvironment.getConfigurations(); - - Map> identifiersByModule = new HashMap<>(); - for (Data dataDto : envelope.data) { - TestIdentifierJson testIdJson = dataDto.attributes; - if (testIdJson == null) { - continue; - } - Configurations conf = testIdJson.getConfigurations(); - String moduleName = - (conf != null && conf.getTestBundle() != null ? conf : requestConf).getTestBundle(); - identifiersByModule - .computeIfAbsent(moduleName, k -> new HashMap<>()) - .put(testIdJson.toTestIdentifier(), testIdJson.toTestMetadata()); + MultiEnvelope envelope = + readEnvelope(skippableTestsPath, testIdentifiersAdapter); + if (envelope == null || envelope.data == null) { + return SkippableTests.EMPTY; } - - String correlationId = envelope.meta != null ? envelope.meta.correlationId : null; - Map coverage = - envelope.meta != null && envelope.meta.coverage != null - ? envelope.meta.coverage - : Collections.emptyMap(); - return new SkippableTests(correlationId, identifiersByModule, coverage); + return SkippableTests.from(envelope, tracerEnvironment); } @Override @@ -156,34 +105,14 @@ public Map> getFlakyTestsByModule(TracerEnvironment LOGGER.debug("Flaky tests file path not provided, returning empty"); return Collections.emptyMap(); } - LOGGER.debug("Reading flaky tests from {}", flakyTestsPath); - try (BufferedSource source = Okio.buffer(Okio.source(flakyTestsPath))) { - MultiEnvelope envelope = testIdentifiersAdapter.fromJson(source); - if (envelope != null && envelope.data != null) { - return toFlakyTestsByModule(envelope, tracerEnvironment); - } - } - return Collections.emptyMap(); - } - - private Map> toFlakyTestsByModule( - MultiEnvelope envelope, TracerEnvironment tracerEnvironment) { - Configurations requestConf = tracerEnvironment.getConfigurations(); - - Map> result = new HashMap<>(); - for (Data dataDto : envelope.data) { - TestIdentifierJson testIdJson = dataDto.attributes; - if (testIdJson == null) { - continue; - } - Configurations conf = testIdJson.getConfigurations(); - String moduleName = - (conf != null && conf.getTestBundle() != null ? conf : requestConf).getTestBundle(); - result - .computeIfAbsent(moduleName, k -> new HashSet<>()) - .add(testIdJson.toTestIdentifier().toFQN()); + MultiEnvelope envelope = + readEnvelope(flakyTestsPath, testIdentifiersAdapter); + if (envelope == null || envelope.data == null) { + return Collections.emptyMap(); } + Map> result = + TestIdentifierJson.toTestFQNsByModule(envelope.data, tracerEnvironment); LOGGER.debug( "Read {} flaky tests from file", result.values().stream().mapToInt(Collection::size).sum()); return result; @@ -197,38 +126,20 @@ public Map> getKnownTestsByModule(TracerEnvironment LOGGER.debug("Known tests file path not provided, returning empty"); return Collections.emptyMap(); } - LOGGER.debug("Reading known tests from {}", knownTestsPath); - try (BufferedSource source = Okio.buffer(Okio.source(knownTestsPath))) { - Envelope envelope = knownTestsAdapter.fromJson(source); - if (envelope != null - && envelope.data != null - && envelope.data.attributes != null - && envelope.data.attributes.tests != null) { - return parseKnownTests(envelope.data.attributes.tests); - } - } - return Collections.emptyMap(); - } - - private Map> parseKnownTests( - Map>> testsMap) { - int count = 0; - Map> result = new HashMap<>(); - for (Map.Entry>> moduleEntry : testsMap.entrySet()) { - String moduleName = moduleEntry.getKey(); - for (Map.Entry> suiteEntry : moduleEntry.getValue().entrySet()) { - String suiteName = suiteEntry.getKey(); - for (String testName : suiteEntry.getValue()) { - result - .computeIfAbsent(moduleName, k -> new HashSet<>()) - .add(new TestFQN(suiteName, testName)); - count++; - } - } + Envelope envelope = readEnvelope(knownTestsPath, knownTestsAdapter); + if (envelope == null + || envelope.data == null + || envelope.data.attributes == null + || envelope.data.attributes.tests == null) { + return Collections.emptyMap(); } - LOGGER.debug("Read {} known tests from file", count); - return count > 0 ? result : null; + Map> result = + KnownTestsResponse.toTestFQNsByModule(envelope.data.attributes.tests); + LOGGER.debug( + "Read {} known tests from file", + result != null ? result.values().stream().mapToInt(Collection::size).sum() : 0); + return result; } @Override @@ -239,56 +150,19 @@ public Map>> getTestManagementTests LOGGER.debug("Test management file path not provided, returning empty"); return Collections.emptyMap(); } - LOGGER.debug("Reading test management data from {}", testManagementPath); - try (BufferedSource source = Okio.buffer(Okio.source(testManagementPath))) { - Envelope envelope = testManagementAdapter.fromJson(source); - if (envelope != null && envelope.data != null && envelope.data.attributes != null) { - return parseTestManagementTests(envelope.data.attributes); - } + Envelope envelope = + readEnvelope(testManagementPath, testManagementAdapter); + if (envelope == null || envelope.data == null || envelope.data.attributes == null) { + return Collections.emptyMap(); } - return Collections.emptyMap(); + return envelope.data.attributes.toTestFQNsBySetting(); } - private Map>> parseTestManagementTests( - TestManagementTestsResponse dto) { - Map> quarantined = new HashMap<>(); - Map> disabled = new HashMap<>(); - Map> attemptToFix = new HashMap<>(); - - for (Map.Entry moduleEntry : - dto.getModules().entrySet()) { - String moduleName = moduleEntry.getKey(); - Map suites = moduleEntry.getValue().getSuites(); - - for (Map.Entry suiteEntry : suites.entrySet()) { - String suiteName = suiteEntry.getKey(); - Map tests = - suiteEntry.getValue().getTests(); - - for (Map.Entry testEntry : - tests.entrySet()) { - String testName = testEntry.getKey(); - TestManagementTestsResponse.Properties props = testEntry.getValue(); - TestFQN fqn = new TestFQN(suiteName, testName); - - if (props.isQuarantined()) { - quarantined.computeIfAbsent(moduleName, k -> new HashSet<>()).add(fqn); - } - if (props.isDisabled()) { - disabled.computeIfAbsent(moduleName, k -> new HashSet<>()).add(fqn); - } - if (props.isAttemptToFix()) { - attemptToFix.computeIfAbsent(moduleName, k -> new HashSet<>()).add(fqn); - } - } - } + @Nullable + private static T readEnvelope(Path path, JsonAdapter adapter) throws IOException { + try (BufferedSource source = Okio.buffer(Okio.source(path))) { + return adapter.fromJson(source); } - - Map>> result = new HashMap<>(); - result.put(TestSetting.QUARANTINED, quarantined); - result.put(TestSetting.DISABLED, disabled); - result.put(TestSetting.ATTEMPT_TO_FIX, attemptToFix); - return result; } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTests.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTests.java index 861bcfde0a0..9e146766d6f 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTests.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/SkippableTests.java @@ -2,8 +2,13 @@ import datadog.trace.api.civisibility.config.TestIdentifier; import datadog.trace.api.civisibility.config.TestMetadata; +import datadog.trace.civisibility.config.api.dto.Data; +import datadog.trace.civisibility.config.api.dto.MultiEnvelope; +import datadog.trace.civisibility.config.api.dto.request.TracerEnvironment; +import datadog.trace.civisibility.config.api.dto.response.TestIdentifierJson; import java.util.BitSet; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -40,4 +45,30 @@ public Map> getIdentifiersByModule() { public Map getCoveredLinesByRelativeSourcePath() { return coveredLinesByRelativeSourcePath; } + + /** + * Builds a {@code SkippableTests} from the backend response envelope (or its file-based + * equivalent), grouping identifiers by module and pulling correlation id and coverage from the + * envelope meta. + */ + public static SkippableTests from( + MultiEnvelope envelope, TracerEnvironment tracerEnvironment) { + Map> identifiersByModule = new HashMap<>(); + for (Data entry : envelope.data) { + TestIdentifierJson identifier = entry.attributes; + if (identifier == null) { + continue; + } + identifiersByModule + .computeIfAbsent(identifier.resolveModuleName(tracerEnvironment), k -> new HashMap<>()) + .put(identifier.toTestIdentifier(), identifier.toTestMetadata()); + } + + String correlationId = envelope.meta != null ? envelope.meta.correlationId : null; + Map coverage = + envelope.meta != null && envelope.meta.coverage != null + ? envelope.meta.coverage + : Collections.emptyMap(); + return new SkippableTests(correlationId, identifiersByModule, coverage); + } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/ConfigurationApiMoshi.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/ConfigurationApiMoshi.java new file mode 100644 index 00000000000..7cc2efcb749 --- /dev/null +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/ConfigurationApiMoshi.java @@ -0,0 +1,28 @@ +package datadog.trace.civisibility.config.api.dto; + +import com.squareup.moshi.Moshi; +import datadog.trace.civisibility.config.CiVisibilitySettings; +import datadog.trace.civisibility.config.ConfigurationsJsonAdapter; +import datadog.trace.civisibility.config.EarlyFlakeDetectionSettings; +import datadog.trace.civisibility.config.TestManagementSettings; +import datadog.trace.civisibility.config.api.dto.response.Meta; + +/** + * Builds the shared Moshi instance used to (de)serialize the Configuration API wire DTOs. Both the + * HTTP and file-based implementations rely on the same envelope structure, so they share the same + * set of registered adapters. + */ +public final class ConfigurationApiMoshi { + + private ConfigurationApiMoshi() {} + + public static Moshi create() { + return new Moshi.Builder() + .add(ConfigurationsJsonAdapter.INSTANCE) + .add(CiVisibilitySettings.JsonAdapter.INSTANCE) + .add(EarlyFlakeDetectionSettings.JsonAdapter.INSTANCE) + .add(TestManagementSettings.JsonAdapter.INSTANCE) + .add(Meta.JsonAdapter.INSTANCE) + .build(); + } +} diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/Data.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/Data.java index 4dddb242654..840be14aa85 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/Data.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/Data.java @@ -10,8 +10,4 @@ public Data(String id, String type, T attributes) { this.type = type; this.attributes = attributes; } - - public T getAttributes() { - return attributes; - } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/KnownTestsResponse.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/KnownTestsResponse.java index 39d4dd510ed..c6d4bdb22f4 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/KnownTestsResponse.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/KnownTestsResponse.java @@ -1,7 +1,11 @@ package datadog.trace.civisibility.config.api.dto.response; import com.squareup.moshi.Json; +import datadog.trace.api.civisibility.config.TestFQN; import datadog.trace.civisibility.config.api.dto.PageInfo; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -31,4 +35,30 @@ public Integer getPageSize() { public String getNextPageCursor() { return pageInfo != null ? pageInfo.cursor : null; } + + /** + * Flattens a {@code module -> suite -> [testName]} map into {@code module -> Set}. + * Returns {@code null} when the input contains no tests so that callers can disable downstream + * features that rely on known tests being available. + */ + @Nullable + public static Map> toTestFQNsByModule( + Map>> tests) { + int totalTests = 0; + Map> testsByModule = new HashMap<>(); + for (Map.Entry>> moduleEntry : tests.entrySet()) { + String moduleName = moduleEntry.getKey(); + for (Map.Entry> suiteEntry : moduleEntry.getValue().entrySet()) { + String suiteName = suiteEntry.getKey(); + List testNames = suiteEntry.getValue(); + totalTests += testNames.size(); + for (String testName : testNames) { + testsByModule + .computeIfAbsent(moduleName, k -> new HashSet<>()) + .add(new TestFQN(suiteName, testName)); + } + } + } + return totalTests > 0 ? testsByModule : null; + } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestIdentifierJson.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestIdentifierJson.java index 9afa856675d..072acd021ab 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestIdentifierJson.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestIdentifierJson.java @@ -2,8 +2,15 @@ import com.squareup.moshi.Json; import datadog.trace.api.civisibility.config.Configurations; +import datadog.trace.api.civisibility.config.TestFQN; import datadog.trace.api.civisibility.config.TestIdentifier; import datadog.trace.api.civisibility.config.TestMetadata; +import datadog.trace.civisibility.config.api.dto.Data; +import datadog.trace.civisibility.config.api.dto.request.TracerEnvironment; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; public final class TestIdentifierJson { @@ -55,4 +62,32 @@ public TestIdentifier toTestIdentifier() { public TestMetadata toTestMetadata() { return new TestMetadata(missingLineCodeCoverage); } + + /** + * Returns the module (test bundle) this identifier belongs to, preferring its own configurations + * when they specify a test bundle and otherwise falling back to the request-level configurations. + */ + public String resolveModuleName(TracerEnvironment tracerEnvironment) { + Configurations requestConf = tracerEnvironment.getConfigurations(); + return (configurations != null && configurations.getTestBundle() != null + ? configurations + : requestConf) + .getTestBundle(); + } + + /** Groups the given test identifiers into a {@code module -> Set} map. */ + public static Map> toTestFQNsByModule( + Collection> data, TracerEnvironment tracerEnvironment) { + Map> testsByModule = new HashMap<>(); + for (Data entry : data) { + TestIdentifierJson identifier = entry.attributes; + if (identifier == null) { + continue; + } + testsByModule + .computeIfAbsent(identifier.resolveModuleName(tracerEnvironment), k -> new HashSet<>()) + .add(identifier.toTestIdentifier().toFQN()); + } + return testsByModule; + } } diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestManagementTestsResponse.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestManagementTestsResponse.java index aeb6bb08a1a..1370f9de162 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestManagementTestsResponse.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/config/api/dto/response/TestManagementTestsResponse.java @@ -1,7 +1,12 @@ package datadog.trace.civisibility.config.api.dto.response; +import datadog.trace.api.civisibility.config.TestFQN; import datadog.trace.civisibility.config.TestSetting; +import java.util.Collection; import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import javax.annotation.Nullable; @@ -13,6 +18,61 @@ public Map getModules() { return modules != null ? modules : Collections.emptyMap(); } + /** + * Returns the contained tests grouped by {@link TestSetting} and module. The result always + * contains an entry for {@link TestSetting#QUARANTINED}, {@link TestSetting#DISABLED} and {@link + * TestSetting#ATTEMPT_TO_FIX} (the inner map may be empty for any of them). + */ + public Map>> toTestFQNsBySetting() { + Map>> result = new EnumMap<>(TestSetting.class); + result.put(TestSetting.QUARANTINED, new HashMap<>()); + result.put(TestSetting.DISABLED, new HashMap<>()); + result.put(TestSetting.ATTEMPT_TO_FIX, new HashMap<>()); + + for (Map.Entry moduleEntry : getModules().entrySet()) { + String moduleName = moduleEntry.getKey(); + for (Map.Entry suiteEntry : moduleEntry.getValue().getSuites().entrySet()) { + String suiteName = suiteEntry.getKey(); + for (Map.Entry testEntry : + suiteEntry.getValue().getTests().entrySet()) { + String testName = testEntry.getKey(); + Properties properties = testEntry.getValue(); + TestFQN fqn = new TestFQN(suiteName, testName); + if (properties.isQuarantined()) { + result + .get(TestSetting.QUARANTINED) + .computeIfAbsent(moduleName, k -> new HashSet<>()) + .add(fqn); + } + if (properties.isDisabled()) { + result + .get(TestSetting.DISABLED) + .computeIfAbsent(moduleName, k -> new HashSet<>()) + .add(fqn); + } + if (properties.isAttemptToFix()) { + result + .get(TestSetting.ATTEMPT_TO_FIX) + .computeIfAbsent(moduleName, k -> new HashSet<>()) + .add(fqn); + } + } + } + } + + return result; + } + + public int totalTestsCount() { + int count = 0; + for (Suites suites : getModules().values()) { + for (Tests tests : suites.getSuites().values()) { + count += tests.getTests().size(); + } + } + return count; + } + public static final class Properties { @Nullable public Map properties;