Check user defined owners file at root directory

* When a non-default OWNERS file name is defined by "ownersFileName",
  a project's master branch root directory should contain such a file,
  or an error message is logged. This could find a suspected Gerrit
  server bug that returns wrong value for ownersFileName.
* Minor clean up of test code.

Change-Id: If99aa065134e1dd4166bd68006bfd63275bfdc22
diff --git a/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnersDb.java b/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnersDb.java
index ca6cae0..59887c9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnersDb.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/findowners/OwnersDb.java
@@ -98,6 +98,20 @@
       ObjectId id = getBranchId(repo, branch, changeData, logs);
       revision = "";
       if (id != null) {
+        if (!ownersFileName.equals(Config.OWNERS) && branch.equals("refs/heads/master")) {
+          // If ownersFileName is not the default "OWNERS", and current branch is master,
+          // this project should have a non-empty root file of that name.
+          // We added this requirement to detect errors in project config files
+          // and Gerrit server bugs that return wrong value of "ownersFileName".
+          String content = getFile(repo, id, "/" + ownersFileName, logs);
+          String found = "Found";
+          if (content.isEmpty()) {
+            String changeId = Config.getChangeId(changeData);
+            logger.atSevere().log("Missing root %s for %s", ownersFileName, changeId);
+            found = "Missing";
+          }
+          logs.add(found + " root " + ownersFileName);
+        }
         for (String fileName : files) {
           // Find OWNERS in fileName's directory and parent directories.
           // Stop looking for a parent directory if OWNERS has "set noparent".
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 38b7d0d..25103e4 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -175,3 +175,7 @@
 [plugin "find-owners"]
     ownersFileName = OWNERS.android
 ```
+
+If ownersFileName is defined the project should have such a specified file
+at the root directory.
+Otherwise it would be considered a configuration or Gerrit server error.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/findowners/FindOwnersIT.java b/src/test/java/com/googlesource/gerrit/plugins/findowners/FindOwnersIT.java
index 80c296b..9f9f625 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/findowners/FindOwnersIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/findowners/FindOwnersIT.java
@@ -27,11 +27,13 @@
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.extensions.api.accounts.EmailInput;
 import com.google.gerrit.extensions.api.changes.SubmitInput;
+import com.google.gerrit.extensions.api.projects.BranchApi;
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeInput;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.account.AccountState;
@@ -365,7 +367,6 @@
   @Test
   public void includeProjectOwnerTest() throws Exception {
     // Test include directive with other project name.
-    String testName = "includeProjectOwnerTest";
     Project.NameKey pA = projectOperations.newProject().create();
     Project.NameKey pB = projectOperations.newProject().create();
     String nameA = pA.get();
@@ -523,14 +524,18 @@
     Project.NameKey pB = projectOperations.newProject().name(name("Project_B")).create();
     // Add OWNERS and OWNERS.alpha file to Project_A.
     switchProject(pA);
+    createBranch("BranchX");
     addFile("1", "OWNERS", "per-file *.c=x@x\n"); // default owner x@x
     addFile("2", "OWNERS.alpha", "per-file *.c=a@a\n"); // alpha owner a@a
     PushOneCommit.Result cA = createChange("cA", "tA.c", "Hello A!");
+    PushOneCommit.Result cX = createChangeInBranch("BranchX", "cX", "tX.c", "Hello X!");
     // Add OWNERS and OWNERS.beta file to Project_B.
     switchProject(pB);
+    createBranch("BranchY");
     addFile("3", "OWNERS", "per-file *.c=y@y\n"); // default owner y@y
     addFile("4", "OWNERS.beta", "per-file *.c=b@b\n"); // beta owner b@b
     PushOneCommit.Result cB = createChange("cB", "tB.c", "Hello B!");
+    PushOneCommit.Result cY = createChangeInBranch("BranchY", "cY", "tY.c", "Hello Y!");
 
     // Default owners file name is "OWNERS".
     assertThat(Config.getDefaultOwnersFileName()).isEqualTo("OWNERS");
@@ -539,8 +544,33 @@
 
     String ownerX = oneOwnerList("x@x");
     String ownerY = oneOwnerList("y@y");
-    assertThat(getOwnersResponse(cA)).contains(ownerX + ", files:[ tA.c ]");
-    assertThat(getOwnersResponse(cB)).contains(ownerY + ", files:[ tB.c ]");
+    String cAResponse = getOwnersDebugResponse(cA);
+    String cXResponse = getOwnersDebugResponse(cX);
+    String cBResponse = getOwnersDebugResponse(cB);
+    String cYResponse = getOwnersDebugResponse(cY);
+    assertThat(cAResponse).contains(ownerX + ", files:[ tA.c ]");
+    assertThat(cBResponse).contains(ownerY + ", files:[ tB.c ]");
+    assertThat(cXResponse).contains(", files:[ tX.c ]");
+    assertThat(cYResponse).contains(", files:[ tY.c ]");
+    assertThat(cXResponse).doesNotContain(ownerX);
+    assertThat(cYResponse).doesNotContain(ownerY);
+    assertThat(cAResponse).contains("branch:refs/heads/master");
+    assertThat(cBResponse).contains("branch:refs/heads/master");
+    assertThat(cXResponse).contains("branch:refs/heads/BranchX");
+    assertThat(cYResponse).contains("branch:refs/heads/BranchY");
+    assertThat(cAResponse).contains("ownersFileName:OWNERS, ");
+    assertThat(cBResponse).contains("ownersFileName:OWNERS, ");
+    assertThat(cXResponse).contains("ownersFileName:OWNERS, ");
+    assertThat(cYResponse).contains("ownersFileName:OWNERS, ");
+
+    // pA and pB use default OWNERS file name.
+    // cA and cB logs should not contain anything about Missing/Found root.
+    assertThat(cAResponse).doesNotContain("root");
+    assertThat(cBResponse).doesNotContain("root");
+    // cX and cY are not for the master branch.
+    // They should not contain anything about Missing/Found root.
+    assertThat(cXResponse).doesNotContain("root");
+    assertThat(cYResponse).doesNotContain("root");
 
     // Change owners file name to "OWNERS.alpha" and "OWNERS.beta"
     switchProject(pA);
@@ -552,22 +582,61 @@
     assertThat(Config.getOwnersFileName(projectCache.get(pB), null)).isEqualTo("OWNERS.beta");
     String ownerA = oneOwnerList("a@a");
     String ownerB = oneOwnerList("b@b");
-    assertThat(getOwnersResponse(cA)).contains(ownerA + ", files:[ tA.c ]");
-    assertThat(getOwnersResponse(cB)).contains(ownerB + ", files:[ tB.c ]");
+    cAResponse = getOwnersDebugResponse(cA);
+    cBResponse = getOwnersDebugResponse(cB);
+    cXResponse = getOwnersDebugResponse(cX);
+    cYResponse = getOwnersDebugResponse(cY);
+    assertThat(cAResponse).contains("ownersFileName:OWNERS.alpha, ");
+    assertThat(cBResponse).contains("ownersFileName:OWNERS.beta, ");
+    assertThat(cXResponse).contains("ownersFileName:OWNERS.alpha, ");
+    assertThat(cYResponse).contains("ownersFileName:OWNERS.beta, ");
+    assertThat(cAResponse).contains(ownerA + ", files:[ tA.c ]");
+    assertThat(cBResponse).contains(ownerB + ", files:[ tB.c ]");
+    // pA and pB now use non-default OWNERS file name.
+    // cA and cB logs should contain "Found root ..."
+    assertThat(cAResponse).contains("FoundrootOWNERS.alpha");
+    assertThat(cBResponse).contains("FoundrootOWNERS.beta");
+    assertThat(cXResponse).doesNotContain("root");
+    assertThat(cYResponse).doesNotContain("root");
+
+    // Now change owners file name to "MAINTAINERS"
+    // logs should contain "Missing root ..."
+    switchProject(pA);
+    setProjectConfig("ownersFileName", "MAINTAINERS");
+    cAResponse = getOwnersDebugResponse(cA);
+    cXResponse = getOwnersDebugResponse(cX);
+    assertThat(cAResponse).contains("ownersFileName:MAINTAINERS, ");
+    assertThat(cXResponse).contains("ownersFileName:MAINTAINERS, ");
+    assertThat(cAResponse).contains("owners:[], ");
+    assertThat(cXResponse).contains("owners:[], ");
+    assertThat(cAResponse).contains("MissingrootMAINTAINERS");
+    // Gerrit server log file should contain: "Missing root MAINTAINERS for change "
+    // cX is not on the master branch, so we do not check for the root owners file.
+    assertThat(cXResponse).doesNotContain("root");
 
     // Change back to OWNERS in Project_A
     switchProject(pA);
     setProjectConfig("ownersFileName", "OWNERS");
     assertThat(Config.getOwnersFileName(projectCache.get(pA), null)).isEqualTo("OWNERS");
-    assertThat(getOwnersResponse(cA)).contains(ownerX + ", files:[ tA.c ]");
-    assertThat(getOwnersResponse(cB)).contains(ownerB + ", files:[ tB.c ]");
+    cAResponse = getOwnersDebugResponse(cA);
+    cBResponse = getOwnersDebugResponse(cB);
+    assertThat(cAResponse).contains(ownerX + ", files:[ tA.c ]");
+    assertThat(cBResponse).contains(ownerB + ", files:[ tB.c ]");
 
     // Change back to OWNERS.alpha in Project_B, but there is no OWNERS.alpha
     switchProject(pB);
     setProjectConfig("ownersFileName", "OWNERS.alpha");
     assertThat(Config.getOwnersFileName(projectCache.get(pB), null)).isEqualTo("OWNERS.alpha");
-    assertThat(getOwnersResponse(cA)).contains(ownerX + ", files:[ tA.c ]");
-    assertThat(getOwnersResponse(cB)).contains("owners:[], files:[ tB.c ]");
+    cAResponse = getOwnersDebugResponse(cA);
+    cBResponse = getOwnersDebugResponse(cB);
+    cYResponse = getOwnersDebugResponse(cY);
+    assertThat(cAResponse).contains("ownersFileName:OWNERS, ");
+    assertThat(cBResponse).contains("ownersFileName:OWNERS.alpha, ");
+    assertThat(cAResponse).contains(ownerX + ", files:[ tA.c ]");
+    assertThat(cBResponse).contains("owners:[], files:[ tB.c ]");
+    assertThat(cBResponse).contains("MissingrootOWNERS.alpha");
+    // Gerrit server log file should contain: "Missing root OWNERS.alpha for change "
+    assertThat(cYResponse).doesNotContain("root");
 
     // Do not accept empty string or all-white-spaces for ownersFileName.
     setProjectConfig("ownersFileName", "   ");
@@ -774,4 +843,14 @@
       assertThat(result.dbgmsgs).isNull();
     }
   }
+
+  private BranchApi createBranch(String branch) throws Exception {
+    return createBranch(new Branch.NameKey(project, branch));
+  }
+
+  private PushOneCommit.Result createChangeInBranch(
+      String branch, String subject, String fileName, String content) throws Exception {
+    PushOneCommit push = pushFactory.create(admin.getIdent(), testRepo, subject, fileName, content);
+    return push.to("refs/for/" + branch);
+  }
 }