Support unreadable files

--
MOS_MIGRATED_REVID=112507181
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java
index 873d48d..d9cce8b 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java
@@ -76,6 +76,8 @@
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -437,6 +439,44 @@
   }
 
   @Test
+  public void testUnreadableFileWithNoFastDigest() throws Exception {
+    Path p = file("unreadable");
+    p.chmod(0);
+    p.setLastModifiedTime(0L);
+
+    FileValue value = valueForPath(p);
+    assertTrue(value.exists());
+    assertThat(value.getDigest()).isNull();
+
+    p.setLastModifiedTime(10L);
+    assertThat(valueForPath(p)).isNotEqualTo(value);
+
+    p.setLastModifiedTime(0L);
+    assertThat(valueForPath(p)).isEqualTo(value);
+  }
+
+  @Test
+  public void testUnreadableFileWithFastDigest() throws Exception {
+    final byte[] expectedDigest = MessageDigest.getInstance("md5").digest(
+        "blah".getBytes(StandardCharsets.UTF_8));
+
+    createFsAndRoot(
+        new CustomInMemoryFs(manualClock) {
+          @Override
+          protected byte[] getFastDigest(Path path) {
+            return path.getBaseName().equals("unreadable") ? expectedDigest : null;
+          }
+        });
+
+    Path p = file("unreadable");
+    p.chmod(0);
+
+    FileValue value = valueForPath(p);
+    assertThat(value.exists()).isTrue();
+    assertThat(value.getDigest()).isNotNull();
+  }
+
+  @Test
   public void testFileModificationModTime() throws Exception {
     fastMd5 = false;
     Path p = file("file");
@@ -882,6 +922,36 @@
     assertThat(errorInfo.getException().getMessage()).contains("/root/a is no longer a file");
   }
 
+  @Test
+  public void testFilesystemInconsistencies_GetFastDigestAndIsReadableFailure() throws Exception {
+    createFsAndRoot(
+        new CustomInMemoryFs(manualClock) {
+          @Override
+          protected boolean isReadable(Path path) throws IOException {
+            if (path.getBaseName().equals("unreadable")) {
+              throw new IOException("isReadable failed");
+            }
+            return super.isReadable(path);
+          }
+        });
+
+    Path p = file("unreadable");
+    p.chmod(0);
+
+    SequentialBuildDriver driver = makeDriver();
+    SkyKey skyKey = skyKey("unreadable");
+    EvaluationResult<FileValue> result =
+        driver.evaluate(
+            ImmutableList.of(skyKey), false, DEFAULT_THREAD_COUNT, NullEventHandler.INSTANCE);
+    assertTrue(result.hasError());
+    ErrorInfo errorInfo = result.getError(skyKey);
+    assertThat(errorInfo.getException()).isInstanceOf(InconsistentFilesystemException.class);
+    assertThat(errorInfo.getException().getMessage())
+        .contains("encountered error 'isReadable failed'");
+    assertThat(errorInfo.getException().getMessage())
+        .contains("/root/unreadable is no longer a file");
+  }
+
   private void runTestSymlinkCycle(boolean ancestorCycle, boolean startInCycle) throws Exception {
     symlink("a", "b");
     symlink("b", "c");