CodeOwnerResolverResult: Include flag if there are unresolved code owners

Android wants a configuration option to make all users code owners of
paths for which no code owners are defined:

1. if for a path no code owners are defined, the path is owned by all
   users
2. if for a path code owners are defined, only these users own the path
3. if for a path code owners are defined, but none of them is resolvable
   the path is owned by no one and changes require an override for
   submission

As a first step for adding such a configuration option we extend
CodeOwnerResolverResult with a flag that is set if there are unresolved
code owners. This allows callers to differentiate the cases above:

1. codeOwners is empty, ownedByAllUsers = false,
   hasUnresolvedCodeOwners = false
2. codeOwner is non-empty or ownedByAllUsers = true
3. codeOwners is empty, ownedByAllUsers = false,
   hasUnresolvedCodeOwners = true

Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: Iab1abf7a139dcead9e8ccad55b989a00d53ff8fc
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
index c6b5f55..205e931 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java
@@ -177,6 +177,7 @@
   public CodeOwnerResolverResult resolve(Set<CodeOwnerReference> codeOwnerReferences) {
     requireNonNull(codeOwnerReferences, "codeOwnerReferences");
     AtomicBoolean ownedByAllUsers = new AtomicBoolean(false);
+    AtomicBoolean hasUnresolvedCodeOwners = new AtomicBoolean(false);
     ImmutableSet<CodeOwner> codeOwners =
         codeOwnerReferences.stream()
             .filter(
@@ -188,10 +189,18 @@
                   return true;
                 })
             .map(this::resolve)
-            .filter(Optional::isPresent)
+            .filter(
+                codeOwner -> {
+                  if (!codeOwner.isPresent()) {
+                    hasUnresolvedCodeOwners.set(true);
+                    return false;
+                  }
+                  return true;
+                })
             .map(Optional::get)
             .collect(toImmutableSet());
-    return CodeOwnerResolverResult.create(codeOwners, ownedByAllUsers.get());
+    return CodeOwnerResolverResult.create(
+        codeOwners, ownedByAllUsers.get(), hasUnresolvedCodeOwners.get());
   }
 
   /**
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java
index 226d541..cd11262 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverResult.java
@@ -43,17 +43,24 @@
    */
   public abstract boolean ownedByAllUsers();
 
+  /** Whether there are code owner references which couldn't be resolved. */
+  public abstract boolean hasUnresolvedCodeOwners();
+
   @Override
   public String toString() {
     return MoreObjects.toStringHelper(this)
         .add("codeOwners", codeOwners())
         .add("ownedByAllUsers", ownedByAllUsers())
+        .add("hasUnresolvedCodeOwners", hasUnresolvedCodeOwners())
         .toString();
   }
 
   /** Creates a {@link CodeOwnerResolverResult} instance. */
   public static CodeOwnerResolverResult create(
-      ImmutableSet<CodeOwner> codeOwners, boolean ownedByAllUsers) {
-    return new AutoValue_CodeOwnerResolverResult(codeOwners, ownedByAllUsers);
+      ImmutableSet<CodeOwner> codeOwners,
+      boolean ownedByAllUsers,
+      boolean hasUnresolvedCodeOwners) {
+    return new AutoValue_CodeOwnerResolverResult(
+        codeOwners, ownedByAllUsers, hasUnresolvedCodeOwners);
   }
 }
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java
index cefe297..86652b2 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java
@@ -192,6 +192,7 @@
         codeOwnerResolver.get().resolvePathCodeOwners(codeOwnerConfig, Paths.get("/README.md"));
     assertThat(result.codeOwners()).isEmpty();
     assertThat(result.ownedByAllUsers()).isFalse();
+    assertThat(result.hasUnresolvedCodeOwners()).isFalse();
   }
 
   @Test
@@ -205,6 +206,7 @@
         codeOwnerResolver.get().resolvePathCodeOwners(codeOwnerConfig, Paths.get("/README.md"));
     assertThat(result.codeOwnersAccountIds()).containsExactly(admin.id(), user.id());
     assertThat(result.ownedByAllUsers()).isFalse();
+    assertThat(result.hasUnresolvedCodeOwners()).isFalse();
   }
 
   @Test
@@ -219,6 +221,7 @@
         codeOwnerResolver.get().resolvePathCodeOwners(codeOwnerConfig, Paths.get("/README.md"));
     assertThat(result.codeOwnersAccountIds()).isEmpty();
     assertThat(result.ownedByAllUsers()).isTrue();
+    assertThat(result.hasUnresolvedCodeOwners()).isFalse();
   }
 
   @Test
@@ -233,6 +236,23 @@
         codeOwnerResolver.get().resolvePathCodeOwners(codeOwnerConfig, Paths.get("/README.md"));
     assertThat(result.codeOwnersAccountIds()).containsExactly(admin.id());
     assertThat(result.ownedByAllUsers()).isFalse();
+    assertThat(result.hasUnresolvedCodeOwners()).isTrue();
+  }
+
+  @Test
+  public void resolvePathCodeOwnersNonResolvableCodeOwnersAreFilteredOutIfOwnedByAllUsers()
+      throws Exception {
+    CodeOwnerConfig codeOwnerConfig =
+        CodeOwnerConfig.builder(CodeOwnerConfig.Key.create(project, "master", "/"), TEST_REVISION)
+            .addCodeOwnerSet(
+                CodeOwnerSet.createWithoutPathExpressions(
+                    "*", admin.email(), "non-existing@example.com"))
+            .build();
+    CodeOwnerResolverResult result =
+        codeOwnerResolver.get().resolvePathCodeOwners(codeOwnerConfig, Paths.get("/README.md"));
+    assertThat(result.codeOwnersAccountIds()).containsExactly(admin.id());
+    assertThat(result.ownedByAllUsers()).isTrue();
+    assertThat(result.hasUnresolvedCodeOwners()).isTrue();
   }
 
   @Test
@@ -386,6 +406,21 @@
                     CodeOwnerReference.create(user.email())));
     assertThat(result.codeOwnersAccountIds()).containsExactly(admin.id(), user.id());
     assertThat(result.ownedByAllUsers()).isFalse();
+    assertThat(result.hasUnresolvedCodeOwners()).isFalse();
+  }
+
+  @Test
+  public void resolveCodeOwnerReferencesNonResolveableCodeOwnersAreFilteredOut() throws Exception {
+    CodeOwnerResolverResult result =
+        codeOwnerResolver
+            .get()
+            .resolve(
+                ImmutableSet.of(
+                    CodeOwnerReference.create(admin.email()),
+                    CodeOwnerReference.create("non-existing@example.com")));
+    assertThat(result.codeOwnersAccountIds()).containsExactly(admin.id());
+    assertThat(result.ownedByAllUsers()).isFalse();
+    assertThat(result.hasUnresolvedCodeOwners()).isTrue();
   }
 
   @Test