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 dfdf2a409e0..69239fce6a6 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 @@ -18,7 +18,7 @@ public CiVisibilitySettings getSettings(TracerEnvironment tracerEnvironment) { @Override public SkippableTests getSkippableTests(TracerEnvironment tracerEnvironment) { - return new SkippableTests(null, Collections.emptyMap(), null); + return SkippableTests.EMPTY; } @Override 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 c89bc7e9813..3e423e62672 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 @@ -29,6 +29,7 @@ import java.util.Base64; import java.util.BitSet; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -191,7 +192,9 @@ public SkippableTests getSkippableTests(TracerEnvironment tracerEnvironment) thr String correlationId = response.meta != null ? response.meta.correlation_id : null; Map coveredLinesByRelativeSourcePath = - response.meta != null ? response.meta.coverage : null; + response.meta != null && response.meta.coverage != null + ? response.meta.coverage + : Collections.emptyMap(); return new SkippableTests( correlationId, testIdentifiersByModule, coveredLinesByRelativeSourcePath); } 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 58030658476..52cd5e0deb4 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 @@ -40,7 +40,7 @@ public class ExecutionSettings { @Nonnull private final EarlyFlakeDetectionSettings earlyFlakeDetectionSettings; @Nullable private final String itrCorrelationId; @Nonnull private final Map skippableTests; - @Nullable private final Map skippableTestsCoverage; + @Nonnull private final Map skippableTestsCoverage; @Nullable private final Collection flakyTests; @Nullable private final Collection knownTests; @Nonnull private final Diff pullRequestDiff; @@ -54,7 +54,7 @@ public ExecutionSettings( @Nonnull EarlyFlakeDetectionSettings earlyFlakeDetectionSettings, @Nullable String itrCorrelationId, @Nonnull Map skippableTests, - @Nullable Map skippableTestsCoverage, + @Nonnull Map skippableTestsCoverage, @Nullable Collection flakyTests, @Nullable Collection knownTests, @Nonnull Diff pullRequestDiff) { @@ -107,7 +107,7 @@ public String getItrCorrelationId() { } /** A bit vector of covered lines by relative source file path. */ - @Nullable + @Nonnull public Map getSkippableTestsCoverage() { return skippableTestsCoverage; } @@ -126,6 +126,10 @@ public Collection getKnownTests() { return knownTests; } + /** + * @return the list of flaky tests for the given module (can be empty), or {@code null} if flaky + * tests could not be obtained + */ @Nullable public Collection getFlakyTests() { return flakyTests; 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 f92fb5274e2..36f6d899a3a 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 @@ -4,7 +4,6 @@ import datadog.trace.api.civisibility.CIConstants; import datadog.trace.api.civisibility.CiVisibilityWellKnownTags; import datadog.trace.api.civisibility.config.TestIdentifier; -import datadog.trace.api.civisibility.config.TestMetadata; import datadog.trace.api.git.GitInfo; import datadog.trace.api.git.GitInfoProvider; import datadog.trace.civisibility.ci.PullRequestInfo; @@ -14,15 +13,19 @@ import datadog.trace.civisibility.git.tree.GitClient; import datadog.trace.civisibility.git.tree.GitDataUploader; import datadog.trace.civisibility.git.tree.GitRepoUnshallow; -import java.nio.file.Path; import java.nio.file.Paths; -import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.function.Function; import javax.annotation.Nonnull; @@ -33,8 +36,11 @@ public class ExecutionSettingsFactoryImpl implements ExecutionSettingsFactory { private static final Logger LOGGER = LoggerFactory.getLogger(ExecutionSettingsFactoryImpl.class); + private static final String TEST_CONFIGURATION_TAG_PREFIX = "test.configuration."; + private static final ThreadFactory THREAD_FACTORY = r -> new Thread(null, r, "dd-ci-vis-config"); + /** * A workaround for bulk-requesting module settings. For any module that has no settings that are * exclusive to it (i.e. that has no skippable/flaky/known tests), the settings will be under this @@ -113,9 +119,31 @@ private TracerEnvironment buildTracerEnvironment( .build(); } - private @Nonnull Map create(TracerEnvironment tracerEnvironment) { + @Nonnull + private Map create(TracerEnvironment tracerEnvironment) { CiVisibilitySettings settings = getCiVisibilitySettings(tracerEnvironment); + ExecutorService settingsExecutor = Executors.newCachedThreadPool(THREAD_FACTORY); + try { + return doCreate(tracerEnvironment, settings, settingsExecutor); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOGGER.error("Interrupted while creating execution settings"); + return Collections.singletonMap(DEFAULT_SETTINGS, ExecutionSettings.EMPTY); + } catch (ExecutionException e) { + LOGGER.error("Error while creating execution settings", e); + return Collections.singletonMap(DEFAULT_SETTINGS, ExecutionSettings.EMPTY); + + } finally { + settingsExecutor.shutdownNow(); + } + } + + @Nonnull + private Map doCreate( + TracerEnvironment tracerEnvironment, CiVisibilitySettings settings, ExecutorService executor) + throws InterruptedException, ExecutionException { boolean itrEnabled = isFeatureEnabled( settings, CiVisibilitySettings::isItrEnabled, Config::isCiVisibilityItrEnabled); @@ -134,7 +162,7 @@ private TracerEnvironment buildTracerEnvironment( settings, CiVisibilitySettings::isFlakyTestRetriesEnabled, Config::isCiVisibilityFlakyRetryEnabled); - boolean impactedTestsDetectionEnabled = + boolean impactedTestsEnabled = isFeatureEnabled( settings, CiVisibilitySettings::isImpactedTestsDetectionEnabled, @@ -167,46 +195,27 @@ private TracerEnvironment buildTracerEnvironment( codeCoverageEnabled, testSkippingEnabled, earlyFlakeDetectionEnabled, - impactedTestsDetectionEnabled, + impactedTestsEnabled, knownTestsRequest, flakyTestRetriesEnabled); - String itrCorrelationId = null; - Map> skippableTestIdentifiers = - Collections.emptyMap(); - Map skippableTestsCoverage = null; - if (itrEnabled && repositoryRoot != null) { - SkippableTests skippableTests = - getSkippableTests(Paths.get(repositoryRoot), tracerEnvironment); - if (skippableTests != null) { - itrCorrelationId = skippableTests.getCorrelationId(); - skippableTestIdentifiers = skippableTests.getIdentifiersByModule(); - skippableTestsCoverage = skippableTests.getCoveredLinesByRelativeSourcePath(); - } - } - - Map> flakyTestsByModule = - flakyTestRetriesEnabled && config.isCiVisibilityFlakyRetryOnlyKnownFlakes() - || CIConstants.FAIL_FAST_TEST_ORDER.equalsIgnoreCase( - config.getCiVisibilityTestOrder()) - ? getFlakyTestsByModule(tracerEnvironment) - : null; - - Map> knownTestsByModule = - knownTestsRequest ? getKnownTestsByModule(tracerEnvironment) : null; - - Set moduleNames = new HashSet<>(Collections.singleton(DEFAULT_SETTINGS)); - moduleNames.addAll(skippableTestIdentifiers.keySet()); - if (flakyTestsByModule != null) { - moduleNames.addAll(flakyTestsByModule.keySet()); - } - if (knownTestsByModule != null) { - moduleNames.addAll(knownTestsByModule.keySet()); - } + Future skippableTestsFuture = + executor.submit(() -> getSkippableTests(tracerEnvironment, itrEnabled)); + Future>> flakyTestsFuture = + executor.submit(() -> getFlakyTestsByModule(tracerEnvironment, flakyTestRetriesEnabled)); + Future>> knownTestsFuture = + executor.submit(() -> getKnownTestsByModule(tracerEnvironment, knownTestsRequest)); + Future pullRequestDiffFuture = + executor.submit(() -> getPullRequestDiff(tracerEnvironment, impactedTestsEnabled)); - Diff pullRequestDiff = getPullRequestDiff(impactedTestsDetectionEnabled, tracerEnvironment); + SkippableTests skippableTests = skippableTestsFuture.get(); + Map> flakyTestsByModule = flakyTestsFuture.get(); + Map> knownTestsByModule = knownTestsFuture.get(); + Diff pullRequestDiff = pullRequestDiffFuture.get(); Map settingsByModule = new HashMap<>(); + Set moduleNames = + getModuleNames(skippableTests, flakyTestsByModule, knownTestsByModule); for (String moduleName : moduleNames) { settingsByModule.put( moduleName, @@ -215,13 +224,15 @@ private TracerEnvironment buildTracerEnvironment( codeCoverageEnabled, testSkippingEnabled, flakyTestRetriesEnabled, - impactedTestsDetectionEnabled, + impactedTestsEnabled, earlyFlakeDetectionEnabled ? settings.getEarlyFlakeDetectionSettings() : EarlyFlakeDetectionSettings.DEFAULT, - itrCorrelationId, - skippableTestIdentifiers.getOrDefault(moduleName, Collections.emptyMap()), - skippableTestsCoverage, + skippableTests.getCorrelationId(), + skippableTests + .getIdentifiersByModule() + .getOrDefault(moduleName, Collections.emptyMap()), + skippableTests.getCoveredLinesByRelativeSourcePath(), flakyTestsByModule != null ? flakyTestsByModule.getOrDefault(moduleName, Collections.emptyList()) : null, @@ -258,9 +269,12 @@ private boolean isFeatureEnabled( return remoteSetting.apply(ciVisibilitySettings) && killSwitch.apply(config); } - @Nullable + @Nonnull private SkippableTests getSkippableTests( - Path repositoryRoot, TracerEnvironment tracerEnvironment) { + TracerEnvironment tracerEnvironment, boolean itrEnabled) { + if (!itrEnabled || repositoryRoot == null) { + return SkippableTests.EMPTY; + } try { // ensure git data upload is finished before asking for tests gitDataUploader @@ -268,37 +282,53 @@ private SkippableTests getSkippableTests( .get(config.getCiVisibilityGitUploadTimeoutMillis(), TimeUnit.MILLISECONDS); SkippableTests skippableTests = configurationApi.getSkippableTests(tracerEnvironment); - LOGGER.debug( - "Received {} skippable tests in total for {}", - skippableTests.getIdentifiersByModule().size(), - repositoryRoot); + + if (LOGGER.isDebugEnabled()) { + int totalSkippableTests = + skippableTests.getIdentifiersByModule().values().stream() + .filter(Objects::nonNull) + .mapToInt(Map::size) + .sum(); + LOGGER.debug( + "Received {} skippable tests in total for {}", + totalSkippableTests, + Paths.get(repositoryRoot)); + } return skippableTests; } catch (InterruptedException e) { Thread.currentThread().interrupt(); LOGGER.error("Interrupted while waiting for git data upload", e); - return null; + return SkippableTests.EMPTY; } catch (Exception e) { LOGGER.error("Could not obtain list of skippable tests, will proceed without skipping", e); - return null; + return SkippableTests.EMPTY; } } + @Nullable private Map> getFlakyTestsByModule( - TracerEnvironment tracerEnvironment) { + TracerEnvironment tracerEnvironment, boolean flakyTestRetriesEnabled) { + if (!(flakyTestRetriesEnabled && config.isCiVisibilityFlakyRetryOnlyKnownFlakes()) + && !CIConstants.FAIL_FAST_TEST_ORDER.equalsIgnoreCase(config.getCiVisibilityTestOrder())) { + return null; + } try { return configurationApi.getFlakyTestsByModule(tracerEnvironment); - } catch (Exception e) { LOGGER.error("Could not obtain list of flaky tests", e); - return Collections.emptyMap(); + return null; } } + @Nullable private Map> getKnownTestsByModule( - TracerEnvironment tracerEnvironment) { + TracerEnvironment tracerEnvironment, boolean knownTestsRequest) { + if (!knownTestsRequest) { + return null; + } try { return configurationApi.getKnownTestsByModule(tracerEnvironment); @@ -310,7 +340,7 @@ private Map> getKnownTestsByModule( @Nonnull private Diff getPullRequestDiff( - boolean impactedTestsDetectionEnabled, TracerEnvironment tracerEnvironment) { + TracerEnvironment tracerEnvironment, boolean impactedTestsDetectionEnabled) { if (!impactedTestsDetectionEnabled) { return LineDiff.EMPTY; } @@ -362,4 +392,20 @@ private Diff getPullRequestDiff( return LineDiff.EMPTY; } + + @Nonnull + private static Set getModuleNames( + SkippableTests skippableTests, + Map> flakyTestsByModule, + Map> knownTestsByModule) { + Set moduleNames = new HashSet<>(Collections.singleton(DEFAULT_SETTINGS)); + moduleNames.addAll(skippableTests.getIdentifiersByModule().keySet()); + if (flakyTestsByModule != null) { + moduleNames.addAll(flakyTestsByModule.keySet()); + } + if (knownTestsByModule != null) { + moduleNames.addAll(knownTestsByModule.keySet()); + } + return moduleNames; + } } 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 664a01905ba..861bcfde0a0 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 @@ -3,19 +3,24 @@ import datadog.trace.api.civisibility.config.TestIdentifier; import datadog.trace.api.civisibility.config.TestMetadata; import java.util.BitSet; +import java.util.Collections; import java.util.Map; +import javax.annotation.Nonnull; import javax.annotation.Nullable; public class SkippableTests { + public static final SkippableTests EMPTY = + new SkippableTests(null, Collections.emptyMap(), Collections.emptyMap()); + private final String correlationId; private final Map> identifiersByModule; private final Map coveredLinesByRelativeSourcePath; public SkippableTests( @Nullable String correlationId, - Map> identifiersByModule, - @Nullable Map coveredLinesByRelativeSourcePath) { + @Nonnull Map> identifiersByModule, + @Nonnull Map coveredLinesByRelativeSourcePath) { this.correlationId = correlationId; this.identifiersByModule = identifiersByModule; this.coveredLinesByRelativeSourcePath = coveredLinesByRelativeSourcePath; @@ -26,11 +31,12 @@ public String getCorrelationId() { return correlationId; } + @Nonnull public Map> getIdentifiersByModule() { return identifiersByModule; } - @Nullable + @Nonnull public Map getCoveredLinesByRelativeSourcePath() { return coveredLinesByRelativeSourcePath; } 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/percentage/JacocoCoverageCalculator.java index fa97538b40e..2fd4096c07d 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/percentage/JacocoCoverageCalculator.java @@ -165,10 +165,7 @@ private void addModuleLayout(BuildModuleLayout moduleLayout) { } /** Handles skipped tests' coverage data received from the backend */ - private void addBackendCoverageData(@Nullable Map skippableTestsCoverage) { - if (skippableTestsCoverage == null) { - return; - } + private void addBackendCoverageData(@Nonnull Map skippableTestsCoverage) { synchronized (coverageDataLock) { for (Map.Entry e : skippableTestsCoverage.entrySet()) { backendCoverageData.merge(e.getKey(), e.getValue(), JacocoCoverageCalculator::mergeBitSets);