Merge "Add support for the new list-owner rest API"
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigParseException.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigParseException.java
index 3de255d..b8fc54b 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigParseException.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerConfigParseException.java
@@ -40,7 +40,12 @@
   /** Returns all validation as a single, formatted string. */
   public String getFullMessage(String defaultCodeOwnerConfigFileName) {
     StringBuilder sb = new StringBuilder(getMessage());
-    sb.append(" '").append(codeOwnerConfigKey.filePath(defaultCodeOwnerConfigFileName)).append("'");
+    sb.append(
+        String.format(
+            " '%s' (project = %s, branch = %s)",
+            codeOwnerConfigKey.filePath(defaultCodeOwnerConfigFileName),
+            codeOwnerConfigKey.project(),
+            codeOwnerConfigKey.shortBranchName()));
     if (!messages.isEmpty()) {
       sb.append(':');
       for (ValidationError msg : messages) {
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnersExceptionHook.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnersExceptionHook.java
index abfc9f5..53d7cd2 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnersExceptionHook.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnersExceptionHook.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.plugins.codeowners.backend.config.InvalidPluginConfigurationException;
 import com.google.gerrit.server.ExceptionHook;
 import java.util.Optional;
+import org.eclipse.jgit.errors.ConfigInvalidException;
 
 /**
  * Class to define the HTTP response status code and message for exceptions that can occur for all
@@ -43,10 +44,18 @@
 
   @Override
   public ImmutableList<String> getUserMessages(Throwable throwable, @Nullable String traceId) {
-    if (isInvalidPluginConfigurationException(throwable)
-        || isInvalidCodeOwnerConfigException(throwable)) {
-      return ImmutableList.of(throwable.getMessage());
+    Optional<InvalidPluginConfigurationException> invalidPluginConfigurationException =
+        getInvalidPluginConfigurationCause(throwable);
+    if (invalidPluginConfigurationException.isPresent()) {
+      return ImmutableList.of(invalidPluginConfigurationException.get().getMessage());
     }
+
+    Optional<ConfigInvalidException> configInvalidException =
+        CodeOwners.getInvalidConfigCause(throwable);
+    if (configInvalidException.isPresent()) {
+      return ImmutableList.of(configInvalidException.get().getMessage());
+    }
+
     return ImmutableList.of();
   }
 
@@ -60,8 +69,15 @@
   }
 
   private static boolean isInvalidPluginConfigurationException(Throwable throwable) {
+    return getInvalidPluginConfigurationCause(throwable).isPresent();
+  }
+
+  private static Optional<InvalidPluginConfigurationException> getInvalidPluginConfigurationCause(
+      Throwable throwable) {
     return Throwables.getCausalChain(throwable).stream()
-        .anyMatch(t -> t instanceof InvalidPluginConfigurationException);
+        .filter(t -> t instanceof InvalidPluginConfigurationException)
+        .map(t -> (InvalidPluginConfigurationException) t)
+        .findFirst();
   }
 
   private static boolean isInvalidCodeOwnerConfigException(Throwable throwable) {
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
index 91e8ca2..55dee9b 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
@@ -169,8 +169,10 @@
                     ImmutableList.of(
                         fatal(
                             String.format(
-                                "invalid code owner config file '%s':\n  %s",
+                                "invalid code owner config file '%s' (project = %s,"
+                                    + " branch = master):\n  %s",
                                 codeOwnerConfigPath,
+                                project,
                                 getParsingErrorMessage(
                                     ImmutableMap.of(
                                         FindOwnersBackend.class,
@@ -560,8 +562,9 @@
         ImmutableList.of(
             fatal(
                 String.format(
-                    "invalid code owner config file '%s':\n  %s",
+                    "invalid code owner config file '%s' (project = %s, branch = master):\n  %s",
                     pathOfNonParseableCodeOwnerConfig,
+                    project,
                     getParsingErrorMessage(
                         ImmutableMap.of(
                             FindOwnersBackend.class,
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesInRevisionIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesInRevisionIT.java
index 3d71b53..0a6c5ad 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesInRevisionIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesInRevisionIT.java
@@ -129,8 +129,9 @@
             ImmutableList.of(
                 fatal(
                     String.format(
-                        "invalid code owner config file '%s':\n  %s",
+                        "invalid code owner config file '%s' (project = %s, branch = master):\n  %s",
                         codeOwnerConfigPath,
+                        project,
                         getParsingErrorMessage(
                             ImmutableMap.of(
                                 FindOwnersBackend.class,
@@ -458,8 +459,9 @@
         ImmutableList.of(
             fatal(
                 String.format(
-                    "invalid code owner config file '%s':\n  %s",
+                    "invalid code owner config file '%s' (project = %s, branch = master):\n  %s",
                     pathOfNonParseableCodeOwnerConfig,
+                    project,
                     getParsingErrorMessage(
                         ImmutableMap.of(
                             FindOwnersBackend.class,
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
index 314747c..82c6127 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnerConfigValidatorIT.java
@@ -407,8 +407,9 @@
         r,
         "invalid code owner config files",
         String.format(
-            "invalid code owner config file '%s':\n  %s",
+            "invalid code owner config file '%s' (project = %s, branch = master):\n  %s",
             codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath(),
+            project,
             getParsingErrorMessage(
                 ImmutableMap.of(
                     FindOwnersBackend.class,
@@ -526,8 +527,9 @@
         r,
         "invalid code owner config files",
         String.format(
-            "invalid code owner config file '%s':\n  %s",
+            "invalid code owner config file '%s' (project = %s, branch = master):\n  %s",
             codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath(),
+            project,
             getParsingErrorMessage(
                 ImmutableMap.of(
                     FindOwnersBackend.class,
@@ -640,8 +642,9 @@
         r,
         "invalid code owner config files",
         String.format(
-            "invalid code owner config file '%s':\n  %s",
+            "invalid code owner config file '%s' (project = %s, branch = master):\n  %s",
             codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).getFilePath(),
+            project,
             getParsingErrorMessage(
                 ImmutableMap.of(
                     FindOwnersBackend.class,
@@ -670,8 +673,9 @@
         r,
         "invalid code owner config files",
         String.format(
-            "invalid code owner config file '%s':\n  %s",
+            "invalid code owner config file '%s' (project = %s, branch = master):\n  %s",
             codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey1).getFilePath(),
+            project,
             getParsingErrorMessage(
                 ImmutableMap.of(
                     FindOwnersBackend.class,
@@ -679,8 +683,9 @@
                     ProtoBackend.class,
                     "1:8: expected \"{\""))),
         String.format(
-            "invalid code owner config file '%s':\n  %s",
+            "invalid code owner config file '%s' (project = %s, branch = master):\n  %s",
             codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey2).getFilePath(),
+            project,
             getParsingErrorMessage(
                 ImmutableMap.of(
                     FindOwnersBackend.class,
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnerStatusRestIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnerStatusRestIT.java
index 7894bf2..a4e6437 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnerStatusRestIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/restapi/GetCodeOwnerStatusRestIT.java
@@ -83,7 +83,8 @@
     assertThat(r.getEntityContent())
         .contains(
             String.format(
-                "invalid code owner config file %s (project = %s, branch = refs/heads/master)",
+                "* invalid code owner config file '%s' (project = %s, branch = master):\n"
+                    + "  invalid line: INVALID",
                 filePath, project.get()));
   }
 
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/AbstractAutoValueTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/AbstractAutoValueTest.java
new file mode 100644
index 0000000..1a3c163
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/AbstractAutoValueTest.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/** Base class for tests of AutoValue classes. */
+abstract class AbstractAutoValueTest extends AbstractCodeOwnersTest {
+  protected <T> void assertThatToStringIncludesAllData(
+      T autoValueObjectToTest, Class<T> autoValueClass) throws Exception {
+    for (Method method : autoValueClass.getDeclaredMethods()) {
+      if (Modifier.isAbstract(method.getModifiers())) {
+        Object result = method.invoke(autoValueObjectToTest);
+        assertThat(autoValueObjectToTest.toString())
+            .contains(String.format("%s=%s", method.getName(), result));
+      }
+    }
+  }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResultTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResultTest.java
new file mode 100644
index 0000000..d64f5bf
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResultTest.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2021 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;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+
+/** Tests for {@link CodeOwnerResolverResult}. */
+public class CodeOwnerResolverResultTest extends AbstractAutoValueTest {
+  @Test
+  public void toStringIncludesAllData() throws Exception {
+    CodeOwnerResolverResult codeOwnerResolverResult =
+        CodeOwnerResolverResult.create(
+            ImmutableSet.of(CodeOwner.create(admin.id())),
+            /* ownedByAllUsers= */ false,
+            /* hasUnresolvedCodeOwners= */ false,
+            /* hasUnresolvedImports= */ false,
+            ImmutableList.of("test message"));
+    assertThatToStringIncludesAllData(codeOwnerResolverResult, CodeOwnerResolverResult.class);
+  }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnersExceptionHookTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnersExceptionHookTest.java
new file mode 100644
index 0000000..e05f8cd
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnersExceptionHookTest.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2021 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;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.truth.OptionalSubject.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
+import com.google.gerrit.plugins.codeowners.backend.config.InvalidPluginConfigurationException;
+import com.google.gerrit.server.ExceptionHook.Status;
+import java.util.Optional;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.junit.Before;
+import org.junit.Test;
+
+/** Tests for {@link CodeOwnersExceptionHook}. */
+public class CodeOwnersExceptionHookTest extends AbstractCodeOwnersTest {
+  private CodeOwnersExceptionHook codeOwnersExceptionHook;
+
+  @Before
+  public void setUpCodeOwnersPlugin() throws Exception {
+    codeOwnersExceptionHook = plugin.getSysInjector().getInstance(CodeOwnersExceptionHook.class);
+  }
+
+  @Test
+  public void skipRetryWithTrace() throws Exception {
+    assertThat(skipRetryWithTrace(newInvalidPluginConfigurationException())).isTrue();
+    assertThat(skipRetryWithTrace(newExceptionWithCause(newInvalidPluginConfigurationException())))
+        .isTrue();
+
+    assertThat(skipRetryWithTrace(newConfigInvalidException())).isTrue();
+    assertThat(skipRetryWithTrace(newExceptionWithCause(newConfigInvalidException()))).isTrue();
+
+    assertThat(skipRetryWithTrace(new Exception())).isFalse();
+    assertThat(skipRetryWithTrace(newExceptionWithCause(new Exception()))).isFalse();
+  }
+
+  @Test
+  public void getUserMessages() throws Exception {
+    InvalidPluginConfigurationException invalidPluginConfigurationException =
+        newInvalidPluginConfigurationException();
+    assertThat(getUserMessages(invalidPluginConfigurationException))
+        .containsExactly(invalidPluginConfigurationException.getMessage());
+    assertThat(getUserMessages(newExceptionWithCause(invalidPluginConfigurationException)))
+        .containsExactly(invalidPluginConfigurationException.getMessage());
+
+    ConfigInvalidException configInvalidException = newConfigInvalidException();
+    assertThat(getUserMessages(configInvalidException))
+        .containsExactly(configInvalidException.getMessage());
+    assertThat(getUserMessages(newExceptionWithCause(configInvalidException)))
+        .containsExactly(configInvalidException.getMessage());
+
+    assertThat(getUserMessages(new Exception())).isEmpty();
+    assertThat(getUserMessages(newExceptionWithCause(new Exception()))).isEmpty();
+  }
+
+  @Test
+  public void getStatus() throws Exception {
+    Status conflictStatus = Status.create(409, "Conflict");
+    assertThat(getStatus(newInvalidPluginConfigurationException()))
+        .value()
+        .isEqualTo(conflictStatus);
+    assertThat(getStatus(newExceptionWithCause(newInvalidPluginConfigurationException())))
+        .value()
+        .isEqualTo(conflictStatus);
+
+    assertThat(getStatus(newConfigInvalidException())).value().isEqualTo(conflictStatus);
+    assertThat(getStatus(newExceptionWithCause(newConfigInvalidException())))
+        .value()
+        .isEqualTo(conflictStatus);
+
+    assertThat(getStatus(new Exception())).isEmpty();
+    assertThat(getStatus(newExceptionWithCause(new Exception()))).isEmpty();
+  }
+
+  private boolean skipRetryWithTrace(Exception exception) {
+    return codeOwnersExceptionHook.skipRetryWithTrace("actionType", "actionName", exception);
+  }
+
+  private ImmutableList<String> getUserMessages(Exception exception) {
+    return codeOwnersExceptionHook.getUserMessages(exception, /* traceId= */ null);
+  }
+
+  private Optional<Status> getStatus(Exception exception) {
+    return codeOwnersExceptionHook.getStatus(exception);
+  }
+
+  private Exception newExceptionWithCause(Exception cause) {
+    return new Exception("exception1", new Exception("exception2", cause));
+  }
+
+  private InvalidPluginConfigurationException newInvalidPluginConfigurationException() {
+    return new InvalidPluginConfigurationException("code-owners", "message");
+  }
+
+  private ConfigInvalidException newConfigInvalidException() {
+    return new ConfigInvalidException("message");
+  }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResultTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResultTest.java
new file mode 100644
index 0000000..d270fdc
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/PathCodeOwnersResultTest.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 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;
+
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Paths;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+/** Tests for {@link PathCodeOwnersResult}. */
+public class PathCodeOwnersResultTest extends AbstractAutoValueTest {
+  private static final ObjectId TEST_REVISION =
+      ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+
+  @Test
+  public void toStringIncludesAllData() throws Exception {
+    CodeOwnerConfig.Key codeOwnerConfigKey = CodeOwnerConfig.Key.create(project, "master", "/");
+    CodeOwnerConfigReference codeOwnerConfigReference =
+        CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS");
+    PathCodeOwnersResult pathCodeOwnersResult =
+        PathCodeOwnersResult.create(
+            Paths.get("/foo/bar/baz.md"),
+            CodeOwnerConfig.builder(codeOwnerConfigKey, TEST_REVISION)
+                .addImport(codeOwnerConfigReference)
+                .build(),
+            ImmutableList.of(
+                UnresolvedImport.create(
+                    codeOwnerConfigKey,
+                    CodeOwnerConfig.Key.create(project, "master", "/bar/"),
+                    codeOwnerConfigReference,
+                    "test message")));
+    assertThatToStringIncludesAllData(pathCodeOwnersResult, PathCodeOwnersResult.class);
+  }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/UnresolvedImportTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/UnresolvedImportTest.java
new file mode 100644
index 0000000..ce0494f
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/UnresolvedImportTest.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2021 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;
+
+import org.junit.Test;
+
+/** Tests for {@link UnresolvedImport}. */
+public class UnresolvedImportTest extends AbstractAutoValueTest {
+  @Test
+  public void toStringIncludesAllData() throws Exception {
+    UnresolvedImport unresolvedImport =
+        UnresolvedImport.create(
+            CodeOwnerConfig.Key.create(project, "master", "/"),
+            CodeOwnerConfig.Key.create(project, "master", "/bar/"),
+            CodeOwnerConfigReference.create(CodeOwnerConfigImportMode.ALL, "/bar/OWNERS"),
+            "test message");
+    assertThatToStringIncludesAllData(unresolvedImport, UnresolvedImport.class);
+  }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/config/AbstractRequiredApprovalConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/config/AbstractRequiredApprovalConfigTest.java
index f1da349..079f3b4 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/config/AbstractRequiredApprovalConfigTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/config/AbstractRequiredApprovalConfigTest.java
@@ -22,9 +22,6 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
-import com.google.gerrit.plugins.codeowners.backend.config.AbstractRequiredApprovalConfig;
-import com.google.gerrit.plugins.codeowners.backend.config.InvalidPluginConfigurationException;
-import com.google.gerrit.plugins.codeowners.backend.config.RequiredApproval;
 import com.google.gerrit.server.git.validators.CommitValidationMessage;
 import com.google.gerrit.server.git.validators.ValidationMessage;
 import com.google.gerrit.server.project.ProjectLevelConfig;
@@ -32,9 +29,16 @@
 import org.eclipse.jgit.lib.Config;
 import org.junit.Test;
 
-/** Tests for subclasses of {@link AbstractRequiredApprovalConfig}. */
+/**
+ * Tests for subclasses of {@link
+ * com.google.gerrit.plugins.codeowners.backend.config.AbstractRequiredApprovalConfig}.
+ */
 public abstract class AbstractRequiredApprovalConfigTest extends AbstractCodeOwnersTest {
-  /** Must return the {@link AbstractRequiredApprovalConfig} that should be tested. */
+  /**
+   * Must return the {@link
+   * com.google.gerrit.plugins.codeowners.backend.config.AbstractRequiredApprovalConfig} that should
+   * be tested.
+   */
   protected abstract AbstractRequiredApprovalConfig getRequiredApprovalConfig();
 
   protected void testCannotGetIfGlobalConfigIsInvalid(String invalidValue) throws Exception {
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/config/BackendConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/config/BackendConfigTest.java
index 761f7da..9852676 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/config/BackendConfigTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/config/BackendConfigTest.java
@@ -26,8 +26,6 @@
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
 import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackendId;
-import com.google.gerrit.plugins.codeowners.backend.config.BackendConfig;
-import com.google.gerrit.plugins.codeowners.backend.config.InvalidPluginConfigurationException;
 import com.google.gerrit.plugins.codeowners.backend.findowners.FindOwnersBackend;
 import com.google.gerrit.plugins.codeowners.backend.proto.ProtoBackend;
 import com.google.gerrit.server.git.validators.CommitValidationMessage;
@@ -37,7 +35,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
-/** Tests for {@link BackendConfig}. */
+/** Tests for {@link com.google.gerrit.plugins.codeowners.backend.config.BackendConfig}. */
 public class BackendConfigTest extends AbstractCodeOwnersTest {
   private BackendConfig backendConfig;
 
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigurationTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigurationTest.java
index 9883fba..72d6d75 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigurationTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigurationTest.java
@@ -40,14 +40,6 @@
 import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigUpdate;
 import com.google.gerrit.plugins.codeowners.backend.FallbackCodeOwners;
 import com.google.gerrit.plugins.codeowners.backend.PathExpressionMatcher;
-import com.google.gerrit.plugins.codeowners.backend.config.BackendConfig;
-import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
-import com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig;
-import com.google.gerrit.plugins.codeowners.backend.config.InvalidPluginConfigurationException;
-import com.google.gerrit.plugins.codeowners.backend.config.OverrideApprovalConfig;
-import com.google.gerrit.plugins.codeowners.backend.config.RequiredApproval;
-import com.google.gerrit.plugins.codeowners.backend.config.RequiredApprovalConfig;
-import com.google.gerrit.plugins.codeowners.backend.config.StatusConfig;
 import com.google.gerrit.plugins.codeowners.backend.findowners.FindOwnersBackend;
 import com.google.gerrit.plugins.codeowners.common.MergeCommitStrategy;
 import com.google.gerrit.server.IdentifiedUser;
@@ -61,7 +53,10 @@
 import org.junit.Before;
 import org.junit.Test;
 
-/** Tests for {@link CodeOwnersPluginConfiguration}. */
+/**
+ * Tests for {@link
+ * com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration}.
+ */
 public class CodeOwnersPluginConfigurationTest extends AbstractCodeOwnersTest {
   @Inject private ProjectOperations projectOperations;
 
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/config/OverrideApprovalConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/config/OverrideApprovalConfigTest.java
index 41a1abc..e2c3243 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/config/OverrideApprovalConfigTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/config/OverrideApprovalConfigTest.java
@@ -19,15 +19,12 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.acceptance.config.GerritConfig;
-import com.google.gerrit.plugins.codeowners.backend.config.AbstractRequiredApprovalConfig;
-import com.google.gerrit.plugins.codeowners.backend.config.OverrideApprovalConfig;
-import com.google.gerrit.plugins.codeowners.backend.config.RequiredApproval;
 import com.google.gerrit.server.project.ProjectState;
 import org.eclipse.jgit.lib.Config;
 import org.junit.Before;
 import org.junit.Test;
 
-/** Tests for {@link OverrideApprovalConfig}. */
+/** Tests for {@link com.google.gerrit.plugins.codeowners.backend.config.OverrideApprovalConfig}. */
 public class OverrideApprovalConfigTest extends AbstractRequiredApprovalConfigTest {
   private OverrideApprovalConfig overrideApprovalConfig;
 
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/config/RequiredApprovalConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/config/RequiredApprovalConfigTest.java
index 3409816..70849ba 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/config/RequiredApprovalConfigTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/config/RequiredApprovalConfigTest.java
@@ -21,15 +21,12 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.acceptance.config.GerritConfig;
-import com.google.gerrit.plugins.codeowners.backend.config.AbstractRequiredApprovalConfig;
-import com.google.gerrit.plugins.codeowners.backend.config.RequiredApproval;
-import com.google.gerrit.plugins.codeowners.backend.config.RequiredApprovalConfig;
 import com.google.gerrit.server.project.ProjectState;
 import org.eclipse.jgit.lib.Config;
 import org.junit.Before;
 import org.junit.Test;
 
-/** Tests for {@link RequiredApprovalConfig}. */
+/** Tests for {@link com.google.gerrit.plugins.codeowners.backend.config.RequiredApprovalConfig}. */
 public class RequiredApprovalConfigTest extends AbstractRequiredApprovalConfigTest {
   private RequiredApprovalConfig requiredApprovalConfig;
 
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/config/RequiredApprovalTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/config/RequiredApprovalTest.java
index 51e160b..ef2e43d 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/config/RequiredApprovalTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/config/RequiredApprovalTest.java
@@ -25,13 +25,12 @@
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.PatchSetApproval;
 import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
-import com.google.gerrit.plugins.codeowners.backend.config.RequiredApproval;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.util.time.TimeUtil;
 import java.util.Arrays;
 import org.junit.Test;
 
-/** Tests for {@link RequiredApproval}. */
+/** Tests for {@link com.google.gerrit.plugins.codeowners.backend.config.RequiredApproval}. */
 public class RequiredApprovalTest extends AbstractCodeOwnersTest {
   @Test
   public void cannotCheckIsCodeOwnerApprovalForNullPatchSetApproval() throws Exception {
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParserTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParserTest.java
index 7864b84..6499fa7 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParserTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParserTest.java
@@ -140,10 +140,12 @@
                         EMAIL_1, "@example.com", "admin@", "admin@example@com", EMAIL_2)));
     assertThat(exception.getFullMessage(FindOwnersBackend.CODE_OWNER_CONFIG_FILE_NAME))
         .isEqualTo(
-            "invalid code owner config file '/OWNERS':\n"
-                + "  invalid line: @example.com\n"
-                + "  invalid line: admin@\n"
-                + "  invalid line: admin@example@com");
+            String.format(
+                "invalid code owner config file '/OWNERS' (project = %s, branch = master):\n"
+                    + "  invalid line: @example.com\n"
+                    + "  invalid line: admin@\n"
+                    + "  invalid line: admin@example@com",
+                project));
   }
 
   @Test
@@ -158,9 +160,11 @@
                     getCodeOwnerConfig(EMAIL_1, "INVALID", "NOT_AN_EMAIL", EMAIL_2)));
     assertThat(exception.getFullMessage(FindOwnersBackend.CODE_OWNER_CONFIG_FILE_NAME))
         .isEqualTo(
-            "invalid code owner config file '/OWNERS':\n"
-                + "  invalid line: INVALID\n"
-                + "  invalid line: NOT_AN_EMAIL");
+            String.format(
+                "invalid code owner config file '/OWNERS' (project = %s, branch = master):\n"
+                    + "  invalid line: INVALID\n"
+                    + "  invalid line: NOT_AN_EMAIL",
+                project));
   }
 
   @Test
@@ -596,7 +600,9 @@
     assertThat(exception.getFullMessage(FindOwnersBackend.CODE_OWNER_CONFIG_FILE_NAME))
         .isEqualTo(
             String.format(
-                "invalid code owner config file '/OWNERS':\n" + "  invalid line: %s", invalidLine));
+                "invalid code owner config file '/OWNERS' (project = %s, branch = master):\n"
+                    + "  invalid line: %s",
+                project, invalidLine));
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/proto/ProtoCodeOwnerConfigParserTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/proto/ProtoCodeOwnerConfigParserTest.java
index 3518a10..3febb50 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/proto/ProtoCodeOwnerConfigParserTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/proto/ProtoCodeOwnerConfigParserTest.java
@@ -90,7 +90,8 @@
                     "owners_config {\n  owner_sets {\nINVALID_LINE\n  }\n}\n"));
     assertThat(exception.getFullMessage(ProtoBackend.CODE_OWNER_CONFIG_FILE_NAME))
         .isEqualTo(
-            "invalid code owner config file '/OWNERS_METADATA':\n" + "  4:3: Expected \"{\".");
+            "invalid code owner config file '/OWNERS_METADATA' (project = project, branch = master):\n"
+                + "  4:3: Expected \"{\".");
   }
 
   @Test