blob: f79bb8f7ee131be36fec798d89e06160ba01e3df [file] [log] [blame]
// 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);
});
}
}