Merge "Improve a11y of code owners status column"
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/GetOwnedPaths.java b/java/com/google/gerrit/plugins/codeowners/restapi/GetOwnedPaths.java
index eda1ee9..bab4ac7 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/GetOwnedPaths.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/GetOwnedPaths.java
@@ -19,7 +19,6 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Streams;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -155,9 +154,7 @@
     info.path = ownedPath.path().toString();
     info.owned = ownedPath.owned() ? true : null;
     info.owners =
-        Streams.stream(ownedPath.owners())
-            .map(GetOwnedPaths::toAccountInfo)
-            .collect(toImmutableList());
+        ownedPath.owners().stream().map(GetOwnedPaths::toAccountInfo).collect(toImmutableList());
     return info;
   }
 
diff --git a/java/com/google/gerrit/plugins/codeowners/testing/OwnedChangedFileInfoSubject.java b/java/com/google/gerrit/plugins/codeowners/testing/OwnedChangedFileInfoSubject.java
index eb6a76e..10276d6 100644
--- a/java/com/google/gerrit/plugins/codeowners/testing/OwnedChangedFileInfoSubject.java
+++ b/java/com/google/gerrit/plugins/codeowners/testing/OwnedChangedFileInfoSubject.java
@@ -63,11 +63,13 @@
   }
 
   public void hasOwnedNewPath(String expectedOwnedNewPath) {
+    @SuppressWarnings("unused")
     var unused = hasNewPathThat(expectedOwnedNewPath);
     check("ownedNewPath").that(ownedChangedFileInfo().newPath.owned).isTrue();
   }
 
   public void hasNonOwnedNewPath(String expectedNonOwnedNewPath) {
+    @SuppressWarnings("unused")
     var unused = hasNewPathThat(expectedNonOwnedNewPath);
     check("ownedNewPath").that(ownedChangedFileInfo().newPath.owned).isNull();
   }
@@ -77,11 +79,13 @@
   }
 
   public void hasOwnedOldPath(String expectedOwnedOldPath) {
+    @SuppressWarnings("unused")
     var unused = hasOldPathThat(expectedOwnedOldPath);
     check("ownedOldPath").that(ownedChangedFileInfo().oldPath.owned).isTrue();
   }
 
   public void hasNonOwnedOldPath(String expectedNonOwnedOldPath) {
+    @SuppressWarnings("unused")
     var unused = hasOldPathThat(expectedNonOwnedOldPath);
     check("ownedOldPath").that(ownedChangedFileInfo().oldPath.owned).isNull();
   }
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckInputTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckInputTest.java
index aa1d939..3c41f14 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckInputTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerApprovalCheckInputTest.java
@@ -18,6 +18,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth8.assertThat;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
@@ -254,6 +255,120 @@
     assertThat(loadInput().checkAllOwners()).isFalse();
   }
 
+  @Test
+  public void createInputForComputingOwnedPaths_noReviewers() throws Exception {
+    changeApi().addReviewer(user.email());
+    changeApi().addReviewer(user2.email());
+
+    // CodeOwnerApprovalCheckInput#createForComputingOwnedPaths never sets reviewers
+    assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).reviewers()).isEmpty();
+  }
+
+  @Test
+  public void createInputForComputingOwnedPaths_noApprovers() throws Exception {
+    // self approve
+    requestScopeOperations.setApiUser(changeOwner);
+    recommend(changeId.toString());
+
+    // approve as user
+    requestScopeOperations.setApiUser(user.id());
+    recommend(changeId.toString());
+
+    // approve as user2
+    requestScopeOperations.setApiUser(user2.id());
+    recommend(changeId.toString());
+
+    // CodeOwnerApprovalCheckInput#createForComputingOwnedPaths always sets approvers to the given
+    // accounts
+    assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).approvers()).isEmpty();
+  }
+
+  @Test
+  public void createInputForComputingOwnedPaths_withApprovers() {
+    // CodeOwnerApprovalCheckInput#createForComputingOwnedPaths always sets approvers to the given
+    // accounts
+    assertThat(
+            createInputForComputingOwnedPaths(ImmutableSet.of(admin.id(), user.id(), user2.id()))
+                .approvers())
+        .containsExactly(admin.id(), user.id(), user2.id());
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true")
+  public void createInputForComputingOwnedPaths_noImplicitApprover() {
+    // CodeOwnerApprovalCheckInput#createForComputingOwnedPaths never sets an implicit approver
+    assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).implicitApprover()).isEmpty();
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1")
+  public void createInputForComputingOwnedPaths_noOverride() throws Exception {
+    createOwnersOverrideLabel();
+
+    ReviewInput reviewInput = new ReviewInput().label("Owners-Override", 1);
+
+    // self override
+    requestScopeOperations.setApiUser(changeOwner);
+    gApi.changes().id(changeId.toString()).current().review(reviewInput);
+
+    // override as user
+    requestScopeOperations.setApiUser(user.id());
+    gApi.changes().id(changeId.toString()).current().review(reviewInput);
+
+    // override as user2
+    requestScopeOperations.setApiUser(user2.id());
+    gApi.changes().id(changeId.toString()).current().review(reviewInput);
+
+    // CodeOwnerApprovalCheckInput#createForComputingOwnedPaths never sets overrides
+    assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).overrides()).isEmpty();
+  }
+
+  @Test
+  public void createInputForComputingOwnedPaths_noGlobalCodeOwners() {
+    CodeOwnerResolverResult globalCodeOwners =
+        createInputForComputingOwnedPaths(ImmutableSet.of()).globalCodeOwners();
+    assertThat(globalCodeOwners.codeOwnersAccountIds()).isEmpty();
+    assertThat(globalCodeOwners.ownedByAllUsers()).isFalse();
+  }
+
+  @Test
+  @GerritConfig(
+      name = "plugin.code-owners.globalCodeOwner",
+      values = {"user1@example.com", "user2@example.com"})
+  public void createInputForComputingOwnedPaths_withGlobalCodeOwners() {
+    CodeOwnerResolverResult globalCodeOwners =
+        createInputForComputingOwnedPaths(ImmutableSet.of()).globalCodeOwners();
+    assertThat(globalCodeOwners.codeOwnersAccountIds()).containsExactly(user.id(), user2.id());
+    assertThat(globalCodeOwners.ownedByAllUsers()).isFalse();
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*")
+  public void createInputForComputingOwnedPaths_withAllUsersAsGlobalCodeOwners() {
+    CodeOwnerResolverResult globalCodeOwners =
+        createInputForComputingOwnedPaths(ImmutableSet.of()).globalCodeOwners();
+    assertThat(globalCodeOwners.codeOwnersAccountIds()).isEmpty();
+    assertThat(globalCodeOwners.ownedByAllUsers()).isTrue();
+  }
+
+  @Test
+  public void createInputForComputingOwnedPaths_withFallbackCodeOwnersNone() {
+    assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).fallbackCodeOwners())
+        .isEqualTo(FallbackCodeOwners.NONE);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "ALL_USERS")
+  public void createInputForComputingOwnedPaths_withFallbackCodeOwnersAllUsers() {
+    assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).fallbackCodeOwners())
+        .isEqualTo(FallbackCodeOwners.ALL_USERS);
+  }
+
+  @Test
+  public void createInputForComputingOwnedPaths_checkAllOwnersIsTrue() {
+    assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).checkAllOwners()).isTrue();
+  }
+
   private void disableSelfCodeReviewApprovals() throws Exception {
     disableSelfApprovals(allProjects, "Code-Review");
   }
@@ -276,4 +391,11 @@
     ChangeNotes changeNotes = changeNotesFactory.create(project, changeId);
     return inputLoaderFactory.create(codeOwnersConfig, codeOwnerResolver, changeNotes).load();
   }
+
+  private CodeOwnerApprovalCheckInput createInputForComputingOwnedPaths(
+      ImmutableSet<Account.Id> accounts) {
+    ChangeNotes changeNotes = changeNotesFactory.create(project, changeId);
+    return CodeOwnerApprovalCheckInput.createForComputingOwnedPaths(
+        codeOwnersConfig, codeOwnerResolver, changeNotes, accounts);
+  }
 }
diff --git a/resources/Documentation/backend-find-owners.md b/resources/Documentation/backend-find-owners.md
index 223cebb..846e8d9 100644
--- a/resources/Documentation/backend-find-owners.md
+++ b/resources/Documentation/backend-find-owners.md
@@ -20,6 +20,21 @@
 details about code owner config files are described
 [here](backends.html#codeOwnerConfigFiles)).
 
+**NOTE:** Tools should never parse `OWNERS` files on their own, but instead
+always use the [REST API](rest-api.html) to access code owner information.
+
+Parsing `OWNERS` files is highly discouraged because:
+
+* it's hard for tools to implement the parsing of `OWNERS` files in a way that
+  is consistent with how Gerrit parses them
+* reimplementing the parsing logic outside of Gerrit is a lot of effort that can
+  be saved by using the REST API and letting Gerrit do the parsing
+* parsing `OWNERS` files outside of Gerrit is likely less efficient since the
+  parser likely needs to reach out to Gerrit to fetch additional input (e.g. if
+  `OWNERS` files from other branches or repositories are imported)
+* the format of `OWNERS` files may change (e.g. by the addition of new features)
+  which could break parsers outside of Gerrit
+
 ### <a id="defaultCodeOwnerConfiguration">Default code owners
 Default code owners that apply to all branches can be defined in an `OWNERS`
 file in the root directory of the `refs/meta/config` branch. This `OWNERS` file
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md
index bc4e0d6..0185851 100644
--- a/resources/Documentation/rest-api.md
+++ b/resources/Documentation/rest-api.md
@@ -914,7 +914,7 @@
 code owner. Note that if the email is configured as global code owner, but the email is not resolvable (see `is_resolvable` field), the user is not a code owner.
 | `is_owned_by_all_users` | Whether the the specified path in the branch is owned by all users (aka `*`).
 | `annotation` | Annotations that were set for the user. Contains only supported annotations (unsupported annotations are reported in the `debugs_logs`). Sorted alphabetically.
-| `debug_logs` | List of debug logs that may help to understand why the user is or isn't a code owner.
+| `debug_logs` | List of debug logs that may help to understand why the user is or isn't a code owner. This information is purely for debugging and the output may be changed at any time. This means bot callers must not parse the debug logs.
 
 ---
 
@@ -1042,7 +1042,7 @@
 | ------------- | -------- | ----------- |
 | `code_owners` |          | List of code owners as [CodeOwnerInfo](#code-owner-info) entities. The code owners are sorted by a score that is computed from mutliple [scoring factors](#scoringFactors).
 | `owned_by_all_users` | optional | Whether the path is owned by all users. Not set if `false`.
-| `debug_logs`  | optional | Debug logs that may help to understand why a user is or isn't suggested as a code owner. Only set if requested via `--debug`.
+| `debug_logs`  | optional | Debug logs that may help to understand why a user is or isn't suggested as a code owner. Only set if requested via `--debug`. This information is purely for debugging and the output may be changed at any time. This means bot callers must not parse the debug logs.
 
 ### <a id="file-code-owner-status-info"> FileCodeOwnerStatusInfo
 The `FileCodeOwnerStatusInfo` entity describes the code owner statuses for a