From c4401da95444d08304295219b72068f77b940630 Mon Sep 17 00:00:00 2001 From: Jagruti Tiwari Date: Wed, 6 Sep 2023 15:55:05 +0530 Subject: [PATCH] Incremental Build detection probe (#358) --- .../IncrementalBuildDetectionProbe.java | 148 ++++++++++++++++++ .../IncrementalBuildDetectionProbeTest.java | 126 +++++++++++++++ .../.mvn/extensions.xml | 7 + .../.mvn/maven.config | 3 + .../.mvn/extensions.xml | 7 + .../.mvn/maven.config | 3 + .../.mvn/maven.config | 2 + .../.mvn/extensions.xml | 7 + .../.mvn/extensions.xml | 7 + .../.mvn/maven.config | 3 + .../.mvn/maven.config | 3 + 11 files changed, 316 insertions(+) create mode 100644 core/src/main/java/io/jenkins/pluginhealth/scoring/probes/IncrementalBuildDetectionProbe.java create mode 100644 core/src/test/java/io/jenkins/pluginhealth/scoring/probes/IncrementalBuildDetectionProbeTest.java create mode 100644 core/src/test/resources/jenkinsci/plugin-repo-with-correct-configuration/.mvn/extensions.xml create mode 100644 core/src/test/resources/jenkinsci/plugin-repo-with-correct-configuration/.mvn/maven.config create mode 100644 core/src/test/resources/jenkinsci/plugin-repo-with-incorrect-configuration-lines-in-both-files/.mvn/extensions.xml create mode 100644 core/src/test/resources/jenkinsci/plugin-repo-with-incorrect-configuration-lines-in-both-files/.mvn/maven.config create mode 100644 core/src/test/resources/jenkinsci/plugin-repo-with-missing-extensions-file/.mvn/maven.config create mode 100644 core/src/test/resources/jenkinsci/plugin-repo-with-missing-maven-config-file/.mvn/extensions.xml create mode 100644 core/src/test/resources/jenkinsci/test-plugin-incorrect-extensions-configuration/.mvn/extensions.xml create mode 100644 core/src/test/resources/jenkinsci/test-plugin-incorrect-maven-configuration/.mvn/maven.config create mode 100644 core/src/test/resources/jenkinsci/test-plugin-with-missing-lines-maven-configuration/.mvn/maven.config diff --git a/core/src/main/java/io/jenkins/pluginhealth/scoring/probes/IncrementalBuildDetectionProbe.java b/core/src/main/java/io/jenkins/pluginhealth/scoring/probes/IncrementalBuildDetectionProbe.java new file mode 100644 index 000000000..813627dd3 --- /dev/null +++ b/core/src/main/java/io/jenkins/pluginhealth/scoring/probes/IncrementalBuildDetectionProbe.java @@ -0,0 +1,148 @@ +/* + * MIT License + * + * Copyright (c) 2023 Jenkins Infra + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.jenkins.pluginhealth.scoring.probes; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import io.jenkins.pluginhealth.scoring.model.Plugin; +import io.jenkins.pluginhealth.scoring.model.ProbeResult; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +/** + * This probe checks for Incremental Build configuration in plugins. + * It looks for configuration in {@code .mvn/maven.config} and {@code .mvn/extensions.xml} files. + * The probe is successful when the configurations are found in both the files. Otherwise, it fails. + */ +@Component +@Order(IncrementalBuildDetectionProbe.ORDER) +public class IncrementalBuildDetectionProbe extends Probe { + public static final int ORDER = SCMLinkValidationProbe.ORDER + 100; + public static final String KEY = "incremental-build-maven-configuration"; + private static final Logger LOGGER = LoggerFactory.getLogger(IncrementalBuildDetectionProbe.class); + private static final String INCREMENTAL_TOOL = "io.jenkins.tools.incrementals"; + private static final String INCREMENTAL_TOOL_ARTIFACT_ID = "git-changelist-maven-extension"; + + @Override + protected ProbeResult doApply(Plugin plugin, ProbeContext context) { + final Path scmRepository = context.getScmRepository(); + final Path mvnConfig = scmRepository.resolve(".mvn"); + if (Files.notExists(mvnConfig)) { + LOGGER.info("Could not find Maven configuration folder {} plugin while running {} probe.", plugin.getName(), key()); + return ProbeResult.failure(key(), String.format("Could not find Maven configuration folder for the %s plugin.", plugin.getName())); + } + + try (Stream incrementalConfigsStream = Files.find(mvnConfig, 1, (path, $) -> Files.isRegularFile(path) && (path.endsWith("maven.config") || path.endsWith("extensions.xml")))) { + List incrementalConfigsStore = incrementalConfigsStream.toList(); + + // Here, we stored the data in a `List`, and we can create a supplier that will create a new `Stream` for us from this data + Supplier> incrementalConfigs = incrementalConfigsStore::stream; + + Optional mavenExtensionsFile = incrementalConfigs.get().filter(path -> path.endsWith("extensions.xml")).findFirst(); + Optional mavenConfigFile = incrementalConfigs.get().filter(path -> path.endsWith("maven.config")).findFirst(); + + if (mavenExtensionsFile.isPresent() && mavenConfigFile.isPresent()) { + return isExtensionsXMLConfigured(mavenExtensionsFile.get()) && isMavenConfigConfigured(mavenConfigFile.get()) + ? ProbeResult.success(key(), String.format("Incremental Build is configured in the %s plugin.", plugin.getName())) + : ProbeResult.failure(key(), String.format("Incremental Build is not configured in the %s plugin.", plugin.getName())); + } + } catch (IOException e) { + LOGGER.error("Could not read files from .mvn directory for {} plugin while running {} probe.", plugin.getName(), key()); + } + return ProbeResult.failure(key(), String.format("Incremental Build is not configured in the %s plugin.", plugin.getName())); + } + + @Override + public String[] getProbeResultRequirement() { + return new String[]{ContinuousDeliveryProbe.KEY}; + } + + @Override + public String key() { + return KEY; + } + + @Override + public String getDescription() { + return "The probe detects whether incremental build is configured in Maven Config."; + } + + /** + * Checks whether `extensions.xml` is configured in the plugin + * + * @param path Looks for extensions.xml configuration in the particular path + * @return true if a correct configuration is found, otherwise false + */ + private boolean isExtensionsXMLConfigured(Path path) { + try { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(path.toFile()); + doc.getDocumentElement().normalize(); + Element rootElement = doc.getDocumentElement(); + Element extensionElement = (Element) rootElement.getElementsByTagName("extension").item(0); + Element groupIdElement = (Element) extensionElement.getElementsByTagName("groupId").item(0); + Element artifactIdElement = (Element) extensionElement.getElementsByTagName("artifactId").item(0); + String groupId = groupIdElement.getTextContent(); + String artifactId = artifactIdElement.getTextContent(); + return INCREMENTAL_TOOL.equals(groupId) && INCREMENTAL_TOOL_ARTIFACT_ID.equals(artifactId); + } catch (IOException e) { + LOGGER.error("Could not read the file during probe {}. {}", key(), e); + } catch (ParserConfigurationException | SAXException e) { + LOGGER.error("Could not parse the file during probe {}. {}", key(), e); + } + return false; + } + + /** + * Checks whether `maven.config` is configured in the plugin + * + * @param path Looks for extensions.xml configuration in the particular path + * @return true if a correct configuration is found, otherwise false + */ + private boolean isMavenConfigConfigured(Path path) { + try { + return Files.readAllLines(path).containsAll(List.of("-Pconsume-incrementals", "-Pmight-produce-incrementals")); + } catch (IOException e) { + LOGGER.error("Could not read the file during probe {}. {}", key(), e); + } + return false; + } +} diff --git a/core/src/test/java/io/jenkins/pluginhealth/scoring/probes/IncrementalBuildDetectionProbeTest.java b/core/src/test/java/io/jenkins/pluginhealth/scoring/probes/IncrementalBuildDetectionProbeTest.java new file mode 100644 index 000000000..f1d06f99e --- /dev/null +++ b/core/src/test/java/io/jenkins/pluginhealth/scoring/probes/IncrementalBuildDetectionProbeTest.java @@ -0,0 +1,126 @@ +package io.jenkins.pluginhealth.scoring.probes; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.nio.file.Path; +import java.util.Map; + +import io.jenkins.pluginhealth.scoring.model.Plugin; +import io.jenkins.pluginhealth.scoring.model.ProbeResult; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class IncrementalBuildDetectionProbeTest extends AbstractProbeTest { + private Plugin plugin; + private ProbeContext ctx; + private IncrementalBuildDetectionProbe probe; + + @BeforeEach + public void init() { + plugin = mock(Plugin.class); + ctx = mock(ProbeContext.class); + probe = getSpy(); + + when(plugin.getDetails()).thenReturn(Map.of( + ContinuousDeliveryProbe.KEY, ProbeResult.success(ContinuousDeliveryProbe.KEY, "") + )); + } + + @Override + IncrementalBuildDetectionProbe getSpy() { + return spy(IncrementalBuildDetectionProbe.class); + } + + @Test + void shouldReturnASuccessfulCheckWhenIncrementalBuildConfiguredInBothFiles() { + when(ctx.getScmRepository()).thenReturn(Path.of("src/test/resources/jenkinsci/plugin-repo-with-correct-configuration")); + when(plugin.getName()).thenReturn("foo"); + assertThat(probe.apply(plugin, ctx)) + .usingRecursiveComparison() + .comparingOnlyFields("id", "message", "status") + .isEqualTo(ProbeResult.success(IncrementalBuildDetectionProbe.KEY, "Incremental Build is configured in the foo plugin.")); + verify(probe).doApply(plugin, ctx); + } + + @Test + void shouldReturnFailureWhenIncrementalBuildIsConfiguredOnlyInExtensionsXML() { + when(ctx.getScmRepository()).thenReturn(Path.of("src/test/resources/jenkinsci/plugin-repo-with-missing-maven-config-file")); + when(plugin.getName()).thenReturn("foo"); + assertThat(probe.apply(plugin, ctx)) + .usingRecursiveComparison() + .comparingOnlyFields("id", "message", "status") + .isEqualTo(ProbeResult.failure(IncrementalBuildDetectionProbe.KEY, "Incremental Build is not configured in the foo plugin.")); + verify(probe).doApply(plugin, ctx); + } + + @Test + void shouldReturnFailureWhenIncrementalBuildIsConfiguredOnlyInMavenConfig() { + when(ctx.getScmRepository()).thenReturn(Path.of("src/test/resources/jenkinsci/plugin-repo-with-missing-extensions-file")); + when(plugin.getName()).thenReturn("foo"); + assertThat(probe.apply(plugin, ctx)) + .usingRecursiveComparison() + .comparingOnlyFields("id", "message", "status") + .isEqualTo(ProbeResult.failure(IncrementalBuildDetectionProbe.KEY, "Incremental Build is not configured in the foo plugin.")); + verify(probe).doApply(plugin, ctx); + } + + @Test + void shouldFailWhenIncrementalBuildIsIncorrectlyConfiguredInBothFiles() { + when(ctx.getScmRepository()).thenReturn(Path.of("src/test/resources/jenkinsci/plugin-repo-with-incorrect-configuration-lines-in-both-files")); + when(plugin.getName()).thenReturn("foo"); + assertThat(probe.apply(plugin, ctx)) + .usingRecursiveComparison() + .comparingOnlyFields("id", "message", "status") + .isEqualTo(ProbeResult.failure(IncrementalBuildDetectionProbe.KEY, "Incremental Build is not configured in the foo plugin.")); + verify(probe).doApply(plugin, ctx); + } + + @Test + void shouldFailWhenIncrementalBuildIsIncorrectlyConfiguredInExtensionsXML() { + when(ctx.getScmRepository()).thenReturn(Path.of("src/test/resources/jenkinsci/test-plugin-incorrect-extensions-configuration")); + when(plugin.getName()).thenReturn("foo"); + assertThat(probe.apply(plugin, ctx)) + .usingRecursiveComparison() + .comparingOnlyFields("id", "message", "status") + .isEqualTo(ProbeResult.failure(IncrementalBuildDetectionProbe.KEY, "Incremental Build is not configured in the foo plugin.")); + verify(probe).doApply(plugin, ctx); + } + + @Test + void shouldFailWhenIncrementalBuildLinesAreIncorrectInMavenConfig() { + when(ctx.getScmRepository()).thenReturn(Path.of("src/test/resources/jenkinsci/test-plugin-incorrect-maven-configuration")); + when(plugin.getName()).thenReturn("foo"); + assertThat(probe.apply(plugin, ctx)) + .usingRecursiveComparison() + .comparingOnlyFields("id", "message", "status") + .isEqualTo(ProbeResult.failure(IncrementalBuildDetectionProbe.KEY, "Incremental Build is not configured in the foo plugin.")); + verify(probe).doApply(plugin, ctx); + } + + @Test + void shouldFailWhenIncrementalBuildLinesAreMissingInMavenConfig() { + when(ctx.getScmRepository()).thenReturn(Path.of("src/test/resources/jenkinsci/test-plugin-with-missing-lines-maven-configuration")); + when(plugin.getName()).thenReturn("foo"); + assertThat(probe.apply(plugin, ctx)) + .usingRecursiveComparison() + .comparingOnlyFields("id", "message", "status") + .isEqualTo(ProbeResult.failure(IncrementalBuildDetectionProbe.KEY, "Incremental Build is not configured in the foo plugin.")); + verify(probe).doApply(plugin, ctx); + } + + @Test + void shouldFailWhenMavenFolderIsNotFound() { + when(ctx.getScmRepository()).thenReturn(Path.of("src/test/resources/jenkinsci/test-repo-without-mvn-should-not-be-found")); + when(plugin.getName()).thenReturn("foo"); + assertThat(probe.apply(plugin, ctx)) + .usingRecursiveComparison() + .comparingOnlyFields("id", "message", "status") + .isEqualTo(ProbeResult.failure(IncrementalBuildDetectionProbe.KEY, "Could not find Maven configuration folder for the foo plugin.")); + verify(probe).doApply(plugin, ctx); + } +} diff --git a/core/src/test/resources/jenkinsci/plugin-repo-with-correct-configuration/.mvn/extensions.xml b/core/src/test/resources/jenkinsci/plugin-repo-with-correct-configuration/.mvn/extensions.xml new file mode 100644 index 000000000..75c0dee8a --- /dev/null +++ b/core/src/test/resources/jenkinsci/plugin-repo-with-correct-configuration/.mvn/extensions.xml @@ -0,0 +1,7 @@ + + + io.jenkins.tools.incrementals + git-changelist-maven-extension + 1.7 + + diff --git a/core/src/test/resources/jenkinsci/plugin-repo-with-correct-configuration/.mvn/maven.config b/core/src/test/resources/jenkinsci/plugin-repo-with-correct-configuration/.mvn/maven.config new file mode 100644 index 000000000..f7daf60d0 --- /dev/null +++ b/core/src/test/resources/jenkinsci/plugin-repo-with-correct-configuration/.mvn/maven.config @@ -0,0 +1,3 @@ +-Pconsume-incrementals +-Pmight-produce-incrementals +-Dchangelist.format=%d.v%s diff --git a/core/src/test/resources/jenkinsci/plugin-repo-with-incorrect-configuration-lines-in-both-files/.mvn/extensions.xml b/core/src/test/resources/jenkinsci/plugin-repo-with-incorrect-configuration-lines-in-both-files/.mvn/extensions.xml new file mode 100644 index 000000000..25a996188 --- /dev/null +++ b/core/src/test/resources/jenkinsci/plugin-repo-with-incorrect-configuration-lines-in-both-files/.mvn/extensions.xml @@ -0,0 +1,7 @@ + + + io.jenkins.tools.incrementals-incorrect + git-changelist-maven-extension + 1.7 + + diff --git a/core/src/test/resources/jenkinsci/plugin-repo-with-incorrect-configuration-lines-in-both-files/.mvn/maven.config b/core/src/test/resources/jenkinsci/plugin-repo-with-incorrect-configuration-lines-in-both-files/.mvn/maven.config new file mode 100644 index 000000000..31ee093ac --- /dev/null +++ b/core/src/test/resources/jenkinsci/plugin-repo-with-incorrect-configuration-lines-in-both-files/.mvn/maven.config @@ -0,0 +1,3 @@ +-Pconsume-incrementals-incorrect +-Pmight-produce-incrementals-incorrect +-Dchangelist.format=%d.v%s diff --git a/core/src/test/resources/jenkinsci/plugin-repo-with-missing-extensions-file/.mvn/maven.config b/core/src/test/resources/jenkinsci/plugin-repo-with-missing-extensions-file/.mvn/maven.config new file mode 100644 index 000000000..295dc3639 --- /dev/null +++ b/core/src/test/resources/jenkinsci/plugin-repo-with-missing-extensions-file/.mvn/maven.config @@ -0,0 +1,2 @@ +-Pmight-produce-incrementals +-Dchangelist.format=%d.v%s diff --git a/core/src/test/resources/jenkinsci/plugin-repo-with-missing-maven-config-file/.mvn/extensions.xml b/core/src/test/resources/jenkinsci/plugin-repo-with-missing-maven-config-file/.mvn/extensions.xml new file mode 100644 index 000000000..75c0dee8a --- /dev/null +++ b/core/src/test/resources/jenkinsci/plugin-repo-with-missing-maven-config-file/.mvn/extensions.xml @@ -0,0 +1,7 @@ + + + io.jenkins.tools.incrementals + git-changelist-maven-extension + 1.7 + + diff --git a/core/src/test/resources/jenkinsci/test-plugin-incorrect-extensions-configuration/.mvn/extensions.xml b/core/src/test/resources/jenkinsci/test-plugin-incorrect-extensions-configuration/.mvn/extensions.xml new file mode 100644 index 000000000..25a996188 --- /dev/null +++ b/core/src/test/resources/jenkinsci/test-plugin-incorrect-extensions-configuration/.mvn/extensions.xml @@ -0,0 +1,7 @@ + + + io.jenkins.tools.incrementals-incorrect + git-changelist-maven-extension + 1.7 + + diff --git a/core/src/test/resources/jenkinsci/test-plugin-incorrect-maven-configuration/.mvn/maven.config b/core/src/test/resources/jenkinsci/test-plugin-incorrect-maven-configuration/.mvn/maven.config new file mode 100644 index 000000000..1eb4b319c --- /dev/null +++ b/core/src/test/resources/jenkinsci/test-plugin-incorrect-maven-configuration/.mvn/maven.config @@ -0,0 +1,3 @@ +-Pconsume-incorrect-incrementals +-Pmight-produce-incorrect-incrementals +-Dchangelist.format=%d.v%s diff --git a/core/src/test/resources/jenkinsci/test-plugin-with-missing-lines-maven-configuration/.mvn/maven.config b/core/src/test/resources/jenkinsci/test-plugin-with-missing-lines-maven-configuration/.mvn/maven.config new file mode 100644 index 000000000..bf3da51e0 --- /dev/null +++ b/core/src/test/resources/jenkinsci/test-plugin-with-missing-lines-maven-configuration/.mvn/maven.config @@ -0,0 +1,3 @@ +-Pconsume-incrementals +-Pmight-incorrect-configuration-incrementals +-Dchangelist.format=%d.v%s