| // Copyright (C) 2020 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.plugins.codeowners.backend.findowners; |
| |
| import static com.google.common.collect.ImmutableList.toImmutableList; |
| import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerConfigSubject.assertThat; |
| import static java.util.stream.Collectors.joining; |
| |
| import com.google.gerrit.entities.Project; |
| import com.google.gerrit.plugins.codeowners.backend.AbstractCodeOwnerConfigParserTest; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigImportMode; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigParser; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigReference; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerReference; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerSet; |
| import com.google.gerrit.plugins.codeowners.testing.CodeOwnerConfigReferenceSubject; |
| import com.google.gerrit.plugins.codeowners.testing.CodeOwnerSetSubject; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import org.junit.Test; |
| |
| /** Tests for {@link FindOwnersCodeOwnerConfigParser}. */ |
| public class FindOwnersCodeOwnerConfigParserTest extends AbstractCodeOwnerConfigParserTest { |
| @Override |
| protected Class<? extends CodeOwnerConfigParser> getCodeOwnerConfigParserClass() { |
| return FindOwnersCodeOwnerConfigParser.class; |
| } |
| |
| @Override |
| protected String getCodeOwnerConfig(CodeOwnerConfig codeOwnerConfig) { |
| StringBuilder b = new StringBuilder(); |
| if (codeOwnerConfig.ignoreParentCodeOwners()) { |
| b.append("set noparent\n"); |
| } |
| |
| codeOwnerConfig |
| .imports() |
| .forEach( |
| codeOwnerConfigReference -> { |
| String keyword; |
| if (codeOwnerConfigReference.importMode().equals(CodeOwnerConfigImportMode.ALL)) { |
| keyword = "include"; |
| } else { |
| keyword = "file:"; |
| } |
| b.append( |
| String.format( |
| "%s %s%s\n", |
| keyword, |
| codeOwnerConfigReference |
| .project() |
| .map(Project.NameKey::get) |
| .map(projectName -> projectName + ":") |
| .orElse(""), |
| codeOwnerConfigReference.filePath())); |
| }); |
| |
| // global code owners |
| for (String email : |
| codeOwnerConfig.codeOwnerSets().stream() |
| .filter(codeOwnerSet -> codeOwnerSet.pathExpressions().isEmpty()) |
| .flatMap(codeOwnerSet -> codeOwnerSet.codeOwners().stream()) |
| .map(CodeOwnerReference::email) |
| .sorted() |
| .distinct() |
| .collect(toImmutableList())) { |
| b.append(email).append('\n'); |
| } |
| |
| // per-file code owners |
| for (CodeOwnerSet codeOwnerSet : |
| codeOwnerConfig.codeOwnerSets().stream() |
| .filter(codeOwnerSet -> !codeOwnerSet.pathExpressions().isEmpty()) |
| .collect(toImmutableList())) { |
| if (codeOwnerSet.ignoreGlobalAndParentCodeOwners()) { |
| b.append( |
| String.format( |
| "per-file %s=set noparent\n", |
| codeOwnerSet.pathExpressions().stream().sorted().collect(joining(",")))); |
| } |
| if (!codeOwnerSet.codeOwners().isEmpty()) { |
| b.append( |
| String.format( |
| "per-file %s=%s\n", |
| codeOwnerSet.pathExpressions().stream().sorted().collect(joining(",")), |
| codeOwnerSet.codeOwners().stream() |
| .map(CodeOwnerReference::email) |
| .sorted() |
| .collect(joining(",")))); |
| } |
| } |
| |
| return b.toString(); |
| } |
| |
| @Test |
| public void codeOwnerConfigWithInvalidEmails_invalidEmailsAreIgnored() throws Exception { |
| assertParseAndFormat( |
| getCodeOwnerConfig(EMAIL_1, "@example.com", "admin@", "admin@example@com", EMAIL_2), |
| codeOwnerConfig -> |
| assertThat(codeOwnerConfig) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(EMAIL_1, EMAIL_2), |
| getCodeOwnerConfig(EMAIL_1, EMAIL_2)); |
| } |
| |
| @Test |
| public void codeOwnerConfigWithInvalidLines_invalidLinesAreIgnored() throws Exception { |
| assertParseAndFormat( |
| getCodeOwnerConfig(EMAIL_1, "INVALID", "NOT_AN_EMAIL", EMAIL_2), |
| codeOwnerConfig -> |
| assertThat(codeOwnerConfig) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(EMAIL_1, EMAIL_2), |
| getCodeOwnerConfig(EMAIL_1, EMAIL_2)); |
| } |
| |
| @Test |
| public void codeOwnerConfigWithInlineComments() throws Exception { |
| assertParseAndFormat( |
| getCodeOwnerConfig(EMAIL_1, EMAIL_2 + " # some comment", EMAIL_3), |
| codeOwnerConfig -> |
| assertThat(codeOwnerConfig) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(EMAIL_1, EMAIL_2, EMAIL_3), |
| getCodeOwnerConfig(EMAIL_1, EMAIL_2, EMAIL_3)); |
| } |
| |
| @Test |
| public void codeOwnerConfigWithNonSortedEmails() throws Exception { |
| assertParseAndFormat( |
| String.join("\n", EMAIL_3, EMAIL_2, EMAIL_1) + "\n", |
| codeOwnerConfig -> |
| assertThat(codeOwnerConfig) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(EMAIL_1, EMAIL_2, EMAIL_3), |
| getCodeOwnerConfig(EMAIL_1, EMAIL_2, EMAIL_3)); |
| } |
| |
| @Test |
| public void setNoParentCanBeSetMultipleTimes() throws Exception { |
| assertParseAndFormat( |
| getCodeOwnerConfig(true, CodeOwnerSet.createWithoutPathExpressions(EMAIL_1)) |
| + "\nset noparent\nset noparent", |
| codeOwnerConfig -> { |
| assertThat(codeOwnerConfig).hasIgnoreParentCodeOwnersThat().isTrue(); |
| assertThat(codeOwnerConfig) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(EMAIL_1); |
| }, |
| getCodeOwnerConfig(true, CodeOwnerSet.createWithoutPathExpressions(EMAIL_1))); |
| } |
| |
| @Test |
| public void codeOwnerSetWithGlobalCodeOwnersIsReturnedFirst() throws Exception { |
| CodeOwnerSet perFileCodeOwnerSet = |
| CodeOwnerSet.builder().addPathExpression("foo").addCodeOwnerEmail(EMAIL_2).build(); |
| CodeOwnerSet globalCodeOwnerSet = CodeOwnerSet.createWithoutPathExpressions(EMAIL_1, EMAIL_3); |
| assertParseAndFormat( |
| getCodeOwnerConfig(false, perFileCodeOwnerSet, globalCodeOwnerSet), |
| codeOwnerConfig -> { |
| assertThat(codeOwnerConfig) |
| .hasCodeOwnerSetsThat() |
| .containsExactly(globalCodeOwnerSet, perFileCodeOwnerSet) |
| .inOrder(); |
| }, |
| getCodeOwnerConfig(false, globalCodeOwnerSet, perFileCodeOwnerSet)); |
| } |
| |
| @Test |
| public void setMultipleCodeOwnerSetsWithoutPathExpressions() throws Exception { |
| CodeOwnerSet codeOwnerSet1 = CodeOwnerSet.createWithoutPathExpressions(EMAIL_1, EMAIL_3); |
| CodeOwnerSet codeOwnerSet2 = CodeOwnerSet.createWithoutPathExpressions(EMAIL_2); |
| assertParseAndFormat( |
| getCodeOwnerConfig(false, codeOwnerSet1, codeOwnerSet2), |
| codeOwnerConfig -> { |
| assertThat(codeOwnerConfig) |
| .hasCodeOwnerSetsThat() |
| .onlyElement() |
| .hasCodeOwnersEmailsThat() |
| .containsExactly(EMAIL_1, EMAIL_2, EMAIL_3); |
| }, |
| // The code owner sets without path expressions are merged into one code owner set. |
| getCodeOwnerConfig( |
| false, CodeOwnerSet.createWithoutPathExpressions(EMAIL_1, EMAIL_2, EMAIL_3))); |
| } |
| |
| @Test |
| public void setNoParentForPathExpressions() throws Exception { |
| CodeOwnerSet codeOwnerSet = |
| CodeOwnerSet.builder() |
| .setIgnoreGlobalAndParentCodeOwners() |
| .addPathExpression("*.md") |
| .addPathExpression("foo") |
| .addCodeOwnerEmail(EMAIL_1) |
| .addCodeOwnerEmail(EMAIL_2) |
| .build(); |
| assertParseAndFormat( |
| getCodeOwnerConfig(false, codeOwnerSet), |
| codeOwnerConfig -> { |
| // we expect 2 code owner sets: |
| // 1. code owner set for line "per-file *.md,foo=set noparent" |
| // 2. code owner set for line "per-file *.md,foo=admin@example.com,jdoe@example.com" |
| assertThat(codeOwnerConfig).hasCodeOwnerSetsThat().hasSize(2); |
| |
| // 1. assert code owner set for line "per-file *.md,foo=set noparent" |
| CodeOwnerSetSubject codeOwnerSet1Subject = |
| assertThat(codeOwnerConfig).hasCodeOwnerSetsThat().element(0); |
| codeOwnerSet1Subject.hasIgnoreGlobalAndParentCodeOwnersThat().isTrue(); |
| codeOwnerSet1Subject.hasPathExpressionsThat().containsExactly("*.md", "foo"); |
| codeOwnerSet1Subject.hasCodeOwnersThat().isEmpty(); |
| |
| // 2. assert code owner set for line "per-file |
| // *.md,foo=admin@example.com,jdoe@example.com" |
| CodeOwnerSetSubject codeOwnerSet2Subject = |
| assertThat(codeOwnerConfig).hasCodeOwnerSetsThat().element(1); |
| codeOwnerSet2Subject.hasIgnoreGlobalAndParentCodeOwnersThat().isFalse(); |
| codeOwnerSet2Subject.hasPathExpressionsThat().containsExactly("*.md", "foo"); |
| codeOwnerSet2Subject.hasCodeOwnersEmailsThat().containsExactly(EMAIL_1, EMAIL_2); |
| }); |
| } |
| |
| @Test |
| public void importCodeOwnerConfigFromSameProject() throws Exception { |
| Path path = Paths.get("/foo/bar/OWNERS"); |
| CodeOwnerConfigReference codeOwnerConfigReference = |
| CodeOwnerConfigReference.builder(CodeOwnerConfigImportMode.ALL, path).build(); |
| assertParseAndFormat( |
| getCodeOwnerConfig(codeOwnerConfigReference), |
| codeOwnerConfig -> { |
| CodeOwnerConfigReferenceSubject codeOwnerConfigReferenceSubject = |
| assertThat(codeOwnerConfig).hasImportsThat().onlyElement(); |
| codeOwnerConfigReferenceSubject.hasProjectThat().isEmpty(); |
| codeOwnerConfigReferenceSubject.hasFilePathThat().isEqualTo(path); |
| }); |
| } |
| |
| @Test |
| public void importCodeOwnerConfigFromOtherProject() throws Exception { |
| String otherProject = "otherProject"; |
| Path path = Paths.get("/foo/bar/OWNERS"); |
| CodeOwnerConfigReference codeOwnerConfigReference = |
| CodeOwnerConfigReference.builder(CodeOwnerConfigImportMode.ALL, path) |
| .setProject(Project.nameKey(otherProject)) |
| .build(); |
| assertParseAndFormat( |
| getCodeOwnerConfig(codeOwnerConfigReference), |
| codeOwnerConfig -> { |
| CodeOwnerConfigReferenceSubject codeOwnerConfigReferenceSubject = |
| assertThat(codeOwnerConfig).hasImportsThat().onlyElement(); |
| codeOwnerConfigReferenceSubject.hasProjectThat().value().isEqualTo(otherProject); |
| codeOwnerConfigReferenceSubject.hasFilePathThat().isEqualTo(path); |
| }); |
| } |
| |
| @Test |
| public void importCodeOwnerConfigWithImportModeAll() throws Exception { |
| CodeOwnerConfigReference codeOwnerConfigReference = |
| CodeOwnerConfigReference.builder(CodeOwnerConfigImportMode.ALL, "/foo/bar/OWNERS").build(); |
| assertParseAndFormat( |
| getCodeOwnerConfig(codeOwnerConfigReference), |
| codeOwnerConfig -> { |
| assertThat(codeOwnerConfig) |
| .hasImportsThat() |
| .onlyElement() |
| .hasImportModeThat() |
| .isEqualTo(CodeOwnerConfigImportMode.ALL); |
| }); |
| } |
| |
| @Test |
| public void importCodeOwnerConfigWithImportModeGlobalCodeOwnerSetsOnly() throws Exception { |
| CodeOwnerConfigReference codeOwnerConfigReference = |
| CodeOwnerConfigReference.builder( |
| CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, "/foo/bar/OWNERS") |
| .build(); |
| assertParseAndFormat( |
| getCodeOwnerConfig(codeOwnerConfigReference), |
| codeOwnerConfig -> { |
| assertThat(codeOwnerConfig) |
| .hasImportsThat() |
| .onlyElement() |
| .hasImportModeThat() |
| .isEqualTo(CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY); |
| }); |
| } |
| |
| @Test |
| public void importMultipleCodeOwnerConfigs() throws Exception { |
| Path path1 = Paths.get("/foo/bar/OWNERS"); |
| CodeOwnerConfigReference codeOwnerConfigReference1 = |
| CodeOwnerConfigReference.builder(CodeOwnerConfigImportMode.ALL, path1).build(); |
| Path path2 = Paths.get("/foo/baz/OWNERS"); |
| CodeOwnerConfigReference codeOwnerConfigReference2 = |
| CodeOwnerConfigReference.builder( |
| CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, path2) |
| .build(); |
| assertParseAndFormat( |
| getCodeOwnerConfig(codeOwnerConfigReference1, codeOwnerConfigReference2), |
| codeOwnerConfig -> { |
| assertThat(codeOwnerConfig).hasImportsThat().hasSize(2); |
| assertThat(codeOwnerConfig) |
| .hasImportsThat() |
| .element(0) |
| .hasFilePathThat() |
| .isEqualTo(path1); |
| assertThat(codeOwnerConfig) |
| .hasImportsThat() |
| .element(1) |
| .hasFilePathThat() |
| .isEqualTo(path2); |
| }); |
| } |
| } |