CheckCodeOwner: Inform in debug logs about matching per-file rules
So far the CheckCodeOwner REST endpoint only tells if a user is a code
owner and through which code owner config file the code ownership is
assigned, but not which of the per-file rules in the code owner config
file matched. Include this info to make debugging easier.
Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: I7bbf9a3136ada646a4f658e4599d82cbe261fd3e
diff --git a/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersIT.java b/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersIT.java
index 26578fc..e42501d 100644
--- a/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersIT.java
+++ b/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersIT.java
@@ -97,6 +97,11 @@
assumeThatCodeOwnersBackendIsNotProtoBackend();
}
+ protected void skipTestIfIgnoreParentCodeOwnersNotSupportedByCodeOwnersBackend() {
+ // the proto backend doesn't support ignoring parent code owners
+ assumeThatCodeOwnersBackendIsNotProtoBackend();
+ }
+
protected void assumeThatCodeOwnersBackendIsNotProtoBackend() {
assume().that(backendConfig.getDefaultBackend()).isNotInstanceOf(ProtoBackend.class);
}
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
index 443b5ef..05843d1 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/PathCodeOwners.java
@@ -34,6 +34,7 @@
import com.google.inject.Singleton;
import java.nio.file.Path;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -202,13 +203,22 @@
CodeOwnerConfig.Builder resolvedCodeOwnerConfigBuilder =
CodeOwnerConfig.builder(codeOwnerConfig.key(), codeOwnerConfig.revision());
+ List<String> messages = new ArrayList<>();
+
// Add all data from the importing code owner config.
resolvedCodeOwnerConfigBuilder.setIgnoreParentCodeOwners(
codeOwnerConfig.ignoreParentCodeOwners());
getGlobalCodeOwnerSets(codeOwnerConfig)
.forEach(resolvedCodeOwnerConfigBuilder::addCodeOwnerSet);
getMatchingPerFileCodeOwnerSets(codeOwnerConfig)
- .forEach(resolvedCodeOwnerConfigBuilder::addCodeOwnerSet);
+ .forEach(
+ codeOwnerSet -> {
+ messages.add(
+ String.format(
+ "per-file code owner set with path expressions %s matches",
+ codeOwnerSet.pathExpressions()));
+ resolvedCodeOwnerConfigBuilder.addCodeOwnerSet(codeOwnerSet);
+ });
List<UnresolvedImport> unresolvedImports =
resolveImports(codeOwnerConfig, resolvedCodeOwnerConfigBuilder);
@@ -219,9 +229,19 @@
// ignoreGlobalAndParentCodeOwners flag set to true.
// In this case also set ignoreParentCodeOwners to true, so that we do not need to inspect the
// ignoreGlobalAndParentCodeOwners flags again.
- if (getMatchingPerFileCodeOwnerSets(resolvedCodeOwnerConfig)
- .anyMatch(CodeOwnerSet::ignoreGlobalAndParentCodeOwners)) {
- logger.atFine().log("remove global code owner sets and set ignoreParentCodeOwners to true");
+ Optional<CodeOwnerSet> matchingPerFileCodeOwnerSetThatIgnoresGlobalAndParentCodeOwners =
+ getMatchingPerFileCodeOwnerSets(resolvedCodeOwnerConfig)
+ .filter(CodeOwnerSet::ignoreGlobalAndParentCodeOwners)
+ .findAny();
+ if (matchingPerFileCodeOwnerSetThatIgnoresGlobalAndParentCodeOwners.isPresent()) {
+ logger.atFine().log("remove folder code owner sets and set ignoreParentCodeOwners to true");
+ messages.add(
+ String.format(
+ "found matching per-file code owner set (with path expressions = %s) that ignores"
+ + " parent code owners, hence ignoring the folder code owners",
+ matchingPerFileCodeOwnerSetThatIgnoresGlobalAndParentCodeOwners
+ .get()
+ .pathExpressions()));
resolvedCodeOwnerConfig =
resolvedCodeOwnerConfig
.toBuilder()
@@ -236,7 +256,7 @@
this.pathCodeOwnersResult =
OptionalResultWithMessages.create(
PathCodeOwnersResult.create(path, resolvedCodeOwnerConfig, unresolvedImports),
- ImmutableList.of());
+ messages);
logger.atFine().log("path code owners result = %s", pathCodeOwnersResult);
return this.pathCodeOwnersResult;
}
diff --git a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerCheckInfoSubject.java b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerCheckInfoSubject.java
index b060198..f9f8e7b 100644
--- a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerCheckInfoSubject.java
+++ b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerCheckInfoSubject.java
@@ -87,6 +87,12 @@
}
}
+ public void hasDebugLogsThatDoNotContainAnyOf(String... expectedMessages) {
+ for (String expectedMessage : expectedMessages) {
+ check("debugLogs").that(codeOwnerCheckInfo().debugLogs).doesNotContain(expectedMessage);
+ }
+ }
+
public IterableSubject hasDebugLogsThat() {
return check("debugLogs").that(codeOwnerCheckInfo().debugLogs);
}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerIT.java
index 8166970..4964186 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerIT.java
@@ -31,12 +31,14 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT;
+import com.google.gerrit.plugins.codeowners.acceptance.testsuite.TestPathExpressions;
import com.google.gerrit.plugins.codeowners.api.CodeOwnerCheckInfo;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackend;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigImportMode;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigReference;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerResolver;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerSet;
import com.google.gerrit.plugins.codeowners.backend.config.BackendConfig;
import com.google.gerrit.plugins.codeowners.backend.findowners.FindOwnersBackend;
import com.google.gerrit.plugins.codeowners.backend.proto.ProtoBackend;
@@ -67,10 +69,12 @@
@Inject private ExternalIdNotes.Factory externalIdNotesFactory;
private BackendConfig backendConfig;
+ private TestPathExpressions testPathExpressions;
@Before
public void setUpCodeOwnersPlugin() throws Exception {
backendConfig = plugin.getSysInjector().getInstance(BackendConfig.class);
+ testPathExpressions = plugin.getSysInjector().getInstance(TestPathExpressions.class);
}
@Test
@@ -641,6 +645,122 @@
projectOperations.project(project).getHead("master").name()));
}
+ @Test
+ public void checkPerFileCodeOwner() throws Exception {
+ TestAccount txtOwner =
+ accountCreator.create(
+ "txtCodeOwner", "txtCodeOwner@example.com", "Txt Code Owner", /* displayName= */ null);
+ TestAccount mdOwner =
+ accountCreator.create(
+ "mdCodeOwner", "mdCodeOwner@example.com", "Md Code Owner", /* displayName= */ null);
+
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/")
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression(testPathExpressions.matchFileType("txt"))
+ .addCodeOwnerEmail(txtOwner.email())
+ .build())
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression(testPathExpressions.matchFileType("md"))
+ .addCodeOwnerEmail(mdOwner.email())
+ .build())
+ .create();
+
+ String path = "/foo/bar/baz.md";
+ CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, mdOwner.email());
+ assertThat(checkCodeOwnerInfo).isCodeOwner();
+ assertThat(checkCodeOwnerInfo).isResolvable();
+ assertThat(checkCodeOwnerInfo)
+ .hasCodeOwnerConfigFilePathsThat()
+ .containsExactly(getCodeOwnerConfigFilePath("/foo/"));
+ assertThat(checkCodeOwnerInfo)
+ .hasDebugLogsThatContainAllOf(
+ String.format(
+ "per-file code owner set with path expressions [%s] matches",
+ testPathExpressions.matchFileType("md")),
+ String.format(
+ "found email %s as code owner in %s",
+ mdOwner.email(), getCodeOwnerConfigFilePath("/foo/")),
+ String.format("resolved to account %s", mdOwner.id()));
+ assertThat(checkCodeOwnerInfo)
+ .hasDebugLogsThatDoNotContainAnyOf(
+ String.format(
+ "per-file code owner set with path expressions [%s] matches",
+ testPathExpressions.matchFileType("txt")));
+ }
+
+ @Test
+ public void checkPerFileCodeOwnerWhenParentCodeOwnersAreIgnored() throws Exception {
+ skipTestIfIgnoreParentCodeOwnersNotSupportedByCodeOwnersBackend();
+
+ TestAccount fileCodeOwner =
+ accountCreator.create(
+ "fileCodeOwner",
+ "fileCodeOwner@example.com",
+ "File Code Owner",
+ /* displayName= */ null);
+ TestAccount folderCodeOwner =
+ accountCreator.create(
+ "folderCodeOwner",
+ "folderCodeOwner@example.com",
+ "Folder Code Owner",
+ /* displayName= */ null);
+
+ codeOwnerConfigOperations
+ .newCodeOwnerConfig()
+ .project(project)
+ .branch("master")
+ .folderPath("/foo/")
+ .addCodeOwnerEmail(folderCodeOwner.email())
+ .addCodeOwnerSet(
+ CodeOwnerSet.builder()
+ .addPathExpression(testPathExpressions.matchFileType("md"))
+ .setIgnoreGlobalAndParentCodeOwners()
+ .addCodeOwnerEmail(fileCodeOwner.email())
+ .build())
+ .create();
+
+ String path = "/foo/bar/baz.md";
+ CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, folderCodeOwner.email());
+ assertThat(checkCodeOwnerInfo).isNotCodeOwner();
+ assertThat(checkCodeOwnerInfo).isResolvable();
+ assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty();
+ assertThat(checkCodeOwnerInfo)
+ .hasDebugLogsThatContainAllOf(
+ String.format(
+ "per-file code owner set with path expressions [%s] matches",
+ testPathExpressions.matchFileType("md")),
+ String.format(
+ "found matching per-file code owner set (with path expressions = [%s]) that ignores"
+ + " parent code owners, hence ignoring the folder code owners",
+ testPathExpressions.matchFileType("md")));
+
+ checkCodeOwnerInfo = checkCodeOwner(path, fileCodeOwner.email());
+ assertThat(checkCodeOwnerInfo).isCodeOwner();
+ assertThat(checkCodeOwnerInfo).isResolvable();
+ assertThat(checkCodeOwnerInfo)
+ .hasCodeOwnerConfigFilePathsThat()
+ .containsExactly(getCodeOwnerConfigFilePath("/foo/"));
+ assertThat(checkCodeOwnerInfo)
+ .hasDebugLogsThatContainAllOf(
+ String.format(
+ "per-file code owner set with path expressions [%s] matches",
+ testPathExpressions.matchFileType("md")),
+ String.format(
+ "found matching per-file code owner set (with path expressions = [%s]) that ignores"
+ + " parent code owners, hence ignoring the folder code owners",
+ testPathExpressions.matchFileType("md")),
+ String.format(
+ "found email %s as code owner in %s",
+ fileCodeOwner.email(), getCodeOwnerConfigFilePath("/foo/")),
+ String.format("resolved to account %s", fileCodeOwner.id()));
+ }
+
private CodeOwnerCheckInfo checkCodeOwner(String path, String email) throws RestApiException {
return checkCodeOwner(path, email, null);
}