Replace sleep with polling in AuthFsHostTest

The FUSE mount isn't ready immediately, so there was a sleep to give it
extra time to be ready. This change replaces the sleep by polling the
filesystem type of FUSE. The polling saves 1 second-ish per test case.

Also fix some setup/teardown problem of an output file.

Bug: 178874539
Test: atest

Change-Id: I8d5b728883a06ada4d903251f63e44d51132643f
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 3837dd3..6a24011 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -23,6 +23,7 @@
 
 import android.platform.test.annotations.RootPermissionTest;
 
+import com.android.compatibility.common.util.PollingCheck;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -53,7 +54,10 @@
     private static final String AUTHFS_BIN = "/apex/com.android.virt/bin/authfs";
 
     /** Plenty of time for authfs to get ready */
-    private static final int TIME_BUDGET_AUTHFS_SETUP = 1500;  // ms
+    private static final int AUTHFS_INIT_TIMEOUT_MS = 1500;
+
+    /** FUSE's magic from statfs(2) */
+    private static final String FUSE_SUPER_MAGIC_HEX = "65735546";
 
     private ITestDevice mDevice;
     private ExecutorService mThreadPool;
@@ -68,7 +72,7 @@
     public void tearDown() throws DeviceNotAvailableException {
         mDevice.executeShellV2Command("killall authfs fd_server");
         mDevice.executeShellV2Command("umount " + MOUNT_DIR);
-        mDevice.executeShellV2Command("rm -f " + TEST_DIR);
+        mDevice.executeShellV2Command("rm -f " + TEST_DIR + "/output");
     }
 
     @Test
@@ -81,7 +85,6 @@
                 + " --local-ro-file 5:input.4k1:input.4k1.merkle_dump:input.4k1.fsv_sig:cert.der"
                 + " --local-ro-file 6:input.4k:input.4k.merkle_dump:input.4k.fsv_sig:cert.der"
         );
-        Thread.sleep(TIME_BUDGET_AUTHFS_SETUP);
 
         // Action
         String actualHashUnverified4m = computeFileHashInGuest(MOUNT_DIR + "/3");
@@ -111,7 +114,6 @@
         runAuthFsInBackground(
                 "--remote-ro-file-unverified 10:6:4194304 --remote-ro-file 11:3:4194304:cert.der"
         );
-        Thread.sleep(TIME_BUDGET_AUTHFS_SETUP);
 
         // Action
         String actualHashUnverified4m = computeFileHashInGuest(MOUNT_DIR + "/10");
@@ -138,7 +140,6 @@
         runAuthFsInBackground(
                 "--remote-ro-file 10:3:4096:cert.der --remote-ro-file 11:6:4097:cert.der"
         );
-        Thread.sleep(TIME_BUDGET_AUTHFS_SETUP);
 
         // Action
         String actualHash4k = computeFileHashInGuest(MOUNT_DIR + "/10");
@@ -161,7 +162,6 @@
                 "--ro-fds 3:4:5"
         );
         runAuthFsInBackground("--remote-ro-file 10:3:4096:cert.der");
-        Thread.sleep(TIME_BUDGET_AUTHFS_SETUP);
 
         // Verify
         assertFalse(copyFileInGuest(MOUNT_DIR + "/10", "/dev/null"));
@@ -173,7 +173,6 @@
         // Setup
         runFdServerInBackground("3<>output", "--rw-fds 3");
         runAuthFsInBackground("--remote-new-rw-file 20:3");
-        Thread.sleep(TIME_BUDGET_AUTHFS_SETUP);
 
         // Action
         String srcPath = "/system/bin/linker";
@@ -195,9 +194,8 @@
     public void testWriteFailedIfDetectsTampering()
             throws DeviceNotAvailableException, InterruptedException {
         // Setup
-        runFdServerInBackground("3<>/output", "--rw-fds 3");
+        runFdServerInBackground("3<>output", "--rw-fds 3");
         runAuthFsInBackground("--remote-new-rw-file 20:3");
-        Thread.sleep(TIME_BUDGET_AUTHFS_SETUP);
 
         String srcPath = "/system/bin/linker";
         String destPath = MOUNT_DIR + "/20";
@@ -249,6 +247,17 @@
         }
     }
 
+    private void throwDowncastedException(Exception e) throws DeviceNotAvailableException {
+        if (e instanceof DeviceNotAvailableException) {
+            throw (DeviceNotAvailableException) e;
+        } else {
+            // Convert the broad Exception into an unchecked exception to avoid polluting all other
+            // methods. waitFor throws Exception because the callback, Callable#call(), has a
+            // signature to throw an Exception.
+            throw new RuntimeException(e);
+        }
+    }
+
     private void runAuthFsInBackground(String flags) throws DeviceNotAvailableException {
         String cmd = "cd " + TEST_DIR + " && " + AUTHFS_BIN + " " + MOUNT_DIR + " " + flags;
 
@@ -261,6 +270,11 @@
                 throw new RuntimeException(e);
             }
         });
+        try {
+            PollingCheck.waitFor(AUTHFS_INIT_TIMEOUT_MS, () -> isRemoteDirectoryOnFuse(MOUNT_DIR));
+        } catch (Exception e) {
+            throwDowncastedException(e);
+        }
     }
 
     private void runFdServerInBackground(String execParamsForOpeningFds, String flags)
@@ -278,6 +292,11 @@
         });
     }
 
+    private boolean isRemoteDirectoryOnFuse(String path) throws DeviceNotAvailableException {
+        String fs_type = expectRemoteCommandToSucceed("stat -f -c '%t' " + path);
+        return FUSE_SUPER_MAGIC_HEX.equals(fs_type);
+    }
+
     private String expectRemoteCommandToSucceed(String cmd) throws DeviceNotAvailableException {
         CommandResult result = mDevice.executeShellV2Command(cmd);
         assertEquals("`" + cmd + "` failed: " + result.getStderr(), CommandStatus.SUCCESS,