Skip to content

Commit

Permalink
Plugin repository ownership based on repository name (#554)
Browse files Browse the repository at this point in the history
  • Loading branch information
alecharp authored Oct 14, 2024
1 parent 2b0ef2c commit b3cf625
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
.map(file -> {
try {
return Files.readAllLines(file).stream()
.anyMatch(line -> line.contains(
"@jenkinsci/%s-plugin-developers".formatted(plugin.getName())))
.anyMatch(line -> line.contains("@jenkinsci/%s-developers"
.formatted(context.getRepositoryName()
.orElse("NOT_VALID"))))
? this.success("CODEOWNERS file is valid.")
: this.success("CODEOWNERS file is not set correctly.");
} catch (IOException ex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2023 Jenkins Infra
* Copyright (c) 2023-2024 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
Expand All @@ -21,7 +21,6 @@
* 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.FileInputStream;
Expand Down Expand Up @@ -56,7 +55,7 @@ public class SCMLinkValidationProbe extends Probe {
public static final int ORDER = UpdateCenterPluginPublicationProbe.ORDER + 100;
public static final String KEY = "scm";
private static final Logger LOGGER = LoggerFactory.getLogger(SCMLinkValidationProbe.class);
private static final String GH_REGEXP = "https://(?<server>[^/]*)/(?<repo>jenkinsci/[^/]*)";
private static final String GH_REGEXP = "https://(?<server>[^/]*)/(?<repo>jenkinsci/[^/]*-plugin)";
public static final Pattern GH_PATTERN = Pattern.compile(GH_REGEXP);

@Override
Expand Down Expand Up @@ -88,7 +87,8 @@ private ProbeResult fromSCMLink(ProbeContext context, String scm, String pluginN
}
try {
context.getGitHub().getRepository(matcher.group("repo"));
Optional<Path> pluginPathInRepository = findPluginPom(context.getScmRepository().get(), pluginName);
Optional<Path> pluginPathInRepository =
findPluginPom(context.getScmRepository().get(), pluginName);
Optional<Path> folderPath = pluginPathInRepository.map(path -> path.getParent());
if (folderPath.isEmpty()) {
return this.success(String.format("No valid POM file found in %s plugin.", pluginName));
Expand Down Expand Up @@ -117,11 +117,9 @@ private Optional<Path> findPluginPom(Path directory, String pluginName) {
* The `maxDepth` is 3 because a lot of plugins aren't located deeper than <root>/plugins/<pom.xml>.
* If the `maxDepth` is more than 3, we will be navigating the `src/main/java/io/jenkins/plugins/artifactId/` folder.
* */
try (Stream<Path> paths = Files.find(directory, 3, (path, $) ->
"pom.xml".equals(path.getFileName().toString()))) {
return paths
.filter(pom -> pomFileMatchesPlugin(pom, pluginName))
.findFirst();
try (Stream<Path> paths = Files.find(
directory, 3, (path, $) -> "pom.xml".equals(path.getFileName().toString()))) {
return paths.filter(pom -> pomFileMatchesPlugin(pom, pluginName)).findFirst();
} catch (IOException e) {
LOGGER.error("Could not browse the folder during probe {}.", pluginName, e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void shouldDetectCodeOwnershipFileWithInvalidContent() throws IOException {
final Plugin plugin = mock(Plugin.class);
final ProbeContext ctx = mock(ProbeContext.class);

when(plugin.getName()).thenReturn("new-super");
when(ctx.getRepositoryName()).thenReturn(Optional.of("new-super-plugin"));

{
final Path repo = Files.createTempDirectory(getClass().getName());
Expand Down Expand Up @@ -102,6 +102,15 @@ void shouldDetectCodeOwnershipFileWithInvalidContent() throws IOException {
""");
when(ctx.getScmRepository()).thenReturn(Optional.of(repo));
}
{
final Path repo = Files.createTempDirectory(getClass().getName());
final Path docs = Files.createDirectory(repo.resolve(".github"));
final Path codeowners = Files.createFile(docs.resolve("CODEOWNERS"));
Files.writeString(codeowners, """
* @alecharp
""");
when(ctx.getScmRepository()).thenReturn(Optional.of(repo));
}

final CodeOwnershipProbe probe = getSpy();

Expand All @@ -117,14 +126,18 @@ void shouldDetectCodeOwnershipFileWithInvalidContent() throws IOException {
.usingRecursiveComparison()
.comparingOnlyFields("id", "status", "message")
.isEqualTo(ProbeResult.success(probe.key(), "CODEOWNERS file is not set correctly.", 0));
assertThat(probe.apply(plugin, ctx))
.usingRecursiveComparison()
.comparingOnlyFields("id", "status", "message")
.isEqualTo(ProbeResult.success(probe.key(), "CODEOWNERS file is not set correctly.", 0));
}

@Test
void shouldDetectCodeOwnershipFileWithValidTeam() throws IOException {
final Plugin plugin = mock(Plugin.class);
final ProbeContext ctx = mock(ProbeContext.class);

when(plugin.getName()).thenReturn("sample");
when(ctx.getRepositoryName()).thenReturn(Optional.of("sample-plugin"));

{
final Path repo = Files.createTempDirectory(getClass().getName());
Expand Down Expand Up @@ -171,4 +184,63 @@ void shouldDetectCodeOwnershipFileWithValidTeam() throws IOException {
.comparingOnlyFields("id", "status", "message")
.isEqualTo(ProbeResult.success(probe.key(), "CODEOWNERS file is valid.", 0));
}

@Test
void shouldDetectCodeOwnershipFileWithValidTeamAndMore() throws IOException {
final Plugin plugin = mock(Plugin.class);
final ProbeContext ctx = mock(ProbeContext.class);

when(ctx.getRepositoryName()).thenReturn(Optional.of("sample-plugin"));

{
final Path repo = Files.createTempDirectory(getClass().getName());
final Path codeowners = Files.createFile(repo.resolve("CODEOWNERS"));
Files.writeString(
codeowners,
"""
* @jenkinsci/sample-plugin-developers
* @alecharp @jenkinsci
""");
when(ctx.getScmRepository()).thenReturn(Optional.of(repo));
}
{
final Path repo = Files.createTempDirectory(getClass().getName());
final Path github = Files.createDirectory(repo.resolve(".github"));
final Path codeowners = Files.createFile(github.resolve("CODEOWNERS"));
Files.writeString(
codeowners,
"""
* @jenkinsci/sample-plugin-developers
* @alecharp @jenkinsci
""");
when(ctx.getScmRepository()).thenReturn(Optional.of(repo));
}
{
final Path repo = Files.createTempDirectory(getClass().getName());
final Path docs = Files.createDirectory(repo.resolve("docs"));
final Path codeowners = Files.createFile(docs.resolve("CODEOWNERS"));
Files.writeString(
codeowners,
"""
* @jenkinsci/sample-plugin-developers
* @alecharp @jenkinsci
""");
when(ctx.getScmRepository()).thenReturn(Optional.of(repo));
}

final CodeOwnershipProbe probe = getSpy();

assertThat(probe.apply(plugin, ctx))
.usingRecursiveComparison()
.comparingOnlyFields("id", "status", "message")
.isEqualTo(ProbeResult.success(probe.key(), "CODEOWNERS file is valid.", 0));
assertThat(probe.apply(plugin, ctx))
.usingRecursiveComparison()
.comparingOnlyFields("id", "status", "message")
.isEqualTo(ProbeResult.success(probe.key(), "CODEOWNERS file is valid.", 0));
assertThat(probe.apply(plugin, ctx))
.usingRecursiveComparison()
.comparingOnlyFields("id", "status", "message")
.isEqualTo(ProbeResult.success(probe.key(), "CODEOWNERS file is valid.", 0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ void shouldRecognizeCorrectGitHubUrl() throws IOException {
final Plugin plugin = mock(Plugin.class);
final ProbeContext ctx = mock(ProbeContext.class);
final GitHub gh = mock(GitHub.class);
final String repositoryName = "jenkinsci/test-repo";
final String repositoryName = "jenkinsci/test-repo-plugin";

when(plugin.getName()).thenReturn("test-repo");
when(plugin.getScm()).thenReturn("https://github.com/" + repositoryName);
Expand All @@ -124,7 +124,7 @@ void shouldRecognizeInvalidGitHubUrl() throws Exception {
final Plugin plugin = mock(Plugin.class);
final ProbeContext ctx = mock(ProbeContext.class);
final GitHub gh = mock(GitHub.class);
final String repositoryName = "jenkinsci/this-is-not-going-to-work";
final String repositoryName = "jenkinsci/this-is-not-going-to-work-plugin";

when(plugin.getScm()).thenReturn("https://github.com/" + repositoryName);
when(plugin.getName()).thenReturn("foo");
Expand All @@ -150,13 +150,13 @@ void shouldBeAbleToFindPluginInModule() throws IOException {
final GitHub gh = mock(GitHub.class);
final GHRepository ghRepo = mock(GHRepository.class);

when(plugin.getScm()).thenReturn("https://github.com/jenkinsci/this-is-fine");
when(plugin.getScm()).thenReturn("https://github.com/jenkinsci/this-is-fine-plugin");
when(plugin.getName()).thenReturn("test-repo");

when(ctx.getScmRepository())
.thenReturn(Optional.of(Path.of("src/test/resources/jenkinsci/test-repo/test-nested-dir-1")));
when(ctx.getGitHub()).thenReturn(gh);
when(gh.getRepository("jenkinsci/this-is-fine")).thenReturn(ghRepo);
when(gh.getRepository("jenkinsci/this-is-fine-plugin")).thenReturn(ghRepo);

final SCMLinkValidationProbe probe = getSpy();
final ProbeResult result = probe.apply(plugin, ctx);
Expand Down

0 comments on commit b3cf625

Please sign in to comment.