Enable microdroid console output with debug level or debug policy

With this CL, pvmfw will add kernel.print.devkmsg and console to
bootargs with hardcoded value from microdroid's debuggable bootconfig.

Test: manually start protected/unprotected VMs \
      with various debug levels and debug policies.
Change-Id: I31600f0d337ff47edfb5cfea60f5e6a010c85ab5
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index f1e5054..d217c00 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -22,6 +22,20 @@
     out: ["avf_debug_policy_without_ramdump.dtbo"],
 }
 
+genrule {
+    name: "test_avf_debug_policy_with_console_output",
+    defaults: ["test_avf_debug_policy_overlay"],
+    srcs: ["assets/avf_debug_policy_with_console_output.dts"],
+    out: ["avf_debug_policy_with_console_output.dtbo"],
+}
+
+genrule {
+    name: "test_avf_debug_policy_without_console_output",
+    defaults: ["test_avf_debug_policy_overlay"],
+    srcs: ["assets/avf_debug_policy_without_console_output.dts"],
+    out: ["avf_debug_policy_without_console_output.dtbo"],
+}
+
 java_test_host {
     name: "MicrodroidHostTestCases",
     srcs: ["java/**/*.java"],
@@ -48,6 +62,8 @@
         ":pvmfw_test",
         ":test_avf_debug_policy_with_ramdump",
         ":test_avf_debug_policy_without_ramdump",
+        ":test_avf_debug_policy_with_console_output",
+        ":test_avf_debug_policy_without_console_output",
         "assets/bcc.dat",
     ],
     data_native_bins: [
diff --git a/tests/hostside/assets/avf_debug_policy_with_console_output.dts b/tests/hostside/assets/avf_debug_policy_with_console_output.dts
new file mode 100644
index 0000000..8cf19d6
--- /dev/null
+++ b/tests/hostside/assets/avf_debug_policy_with_console_output.dts
@@ -0,0 +1,18 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    fragment@avf {
+        target-path = "/";
+
+        __overlay__ {
+            avf {
+                guest {
+                    common {
+                        log = <1>;
+                    };
+                };
+            };
+        };
+    };
+};
\ No newline at end of file
diff --git a/tests/hostside/assets/avf_debug_policy_without_console_output.dts b/tests/hostside/assets/avf_debug_policy_without_console_output.dts
new file mode 100644
index 0000000..da6400c
--- /dev/null
+++ b/tests/hostside/assets/avf_debug_policy_without_console_output.dts
@@ -0,0 +1,18 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    fragment@avf {
+        target-path = "/";
+
+        __overlay__ {
+            avf {
+                guest {
+                    common {
+                        log = <0>;
+                    };
+                };
+            };
+        };
+    };
+};
\ No newline at end of file
diff --git a/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java b/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
index bda52dd..1ada5a1 100644
--- a/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
@@ -19,6 +19,7 @@
 import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assume.assumeTrue;
 
@@ -32,6 +33,7 @@
 import com.android.tradefed.device.TestDevice;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.FileUtil;
 
 import org.junit.After;
@@ -41,6 +43,7 @@
 
 import java.io.File;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 import java.io.FileNotFoundException;
 
 /** Tests debug policy of pvmfw.bin with custom debug policy */
@@ -50,13 +53,16 @@
     @NonNull private static final String BCC_FILE_NAME = "bcc.dat";
     @NonNull private static final String PACKAGE_FILE_NAME = "MicrodroidTestApp.apk";
     @NonNull private static final String PACKAGE_NAME = "com.android.microdroid.test";
-    @NonNull private static final String MICRODROID_DEBUG_LEVEL = "full";
+    @NonNull private static final String MICRODROID_DEBUG_FULL = "full";
     @NonNull private static final String MICRODROID_CONFIG_PATH = "assets/vm_config_apex.json";
-    private static final int BOOT_COMPLETE_TIMEOUT = 30000; // 30 seconds
+    @NonNull private static final String MICRODROID_LOG_PATH = TEST_ROOT + "log.txt";
+    @NonNull private static final String MICRODROID_CONSOLE_PATH = TEST_ROOT + "console.txt";
+    private static final int BOOT_COMPLETE_TIMEOUT_MS = 30000; // 30 seconds
+    private static final int CONSOLE_OUTPUT_WAIT_MS = 5000; // 5 seconds
 
     @NonNull private static final String CUSTOM_PVMFW_FILE_PREFIX = "pvmfw";
     @NonNull private static final String CUSTOM_PVMFW_FILE_SUFFIX = ".bin";
-    @NonNull private static final String CUSTOM_PVMFW_IMG_PATH = TEST_ROOT + "/" + PVMFW_FILE_NAME;
+    @NonNull private static final String CUSTOM_PVMFW_IMG_PATH = TEST_ROOT + PVMFW_FILE_NAME;
     @NonNull private static final String CUSTOM_PVMFW_IMG_PATH_PROP = "hypervisor.pvmfw.path";
 
     @NonNull private static final String MICRODROID_CMDLINE_PATH = "/proc/cmdline";
@@ -162,6 +168,30 @@
                 .isEqualTo(HEX_STRING_ZERO);
     }
 
+    @Test
+    public void testConsoleOutput() throws Exception {
+        Pvmfw pvmfw = createPvmfw("avf_debug_policy_with_console_output.dtbo");
+        pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+
+        CommandResult result = tryLaunchProtectedNonDebuggableVm();
+
+        assertWithMessage("Microdroid's console message should have been enabled")
+                .that(hasConsoleOutput(result))
+                .isTrue();
+    }
+
+    @Test
+    public void testNoConsoleOutput() throws Exception {
+        Pvmfw pvmfw = createPvmfw("avf_debug_policy_without_console_output.dtbo");
+        pvmfw.serialize(mCustomPvmfwBinFileOnHost);
+
+        CommandResult result = tryLaunchProtectedNonDebuggableVm();
+
+        assertWithMessage("Microdroid's console message shouldn't have been disabled")
+                .that(hasConsoleOutput(result))
+                .isFalse();
+    }
+
     @NonNull
     private String readMicrodroidFileAsString(@NonNull String path)
             throws DeviceNotAvailableException {
@@ -184,18 +214,50 @@
                 .build();
     }
 
+    @NonNull
+    private boolean hasConsoleOutput(CommandResult result) throws DeviceNotAvailableException {
+        return result.getStdout().contains("Run /init as init process");
+    }
+
     private ITestDevice launchProtectedVmAndWaitForBootCompleted()
             throws DeviceNotAvailableException {
         mMicrodroidDevice =
                 MicrodroidBuilder.fromDevicePath(
                                 getPathForPackage(PACKAGE_NAME), MICRODROID_CONFIG_PATH)
-                        .debugLevel(MICRODROID_DEBUG_LEVEL)
+                        .debugLevel(MICRODROID_DEBUG_FULL)
                         .protectedVm(/* protectedVm= */ true)
                         .addBootFile(mCustomPvmfwBinFileOnHost, PVMFW_FILE_NAME)
                         .build(mAndroidDevice);
-        assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT)).isTrue();
+        assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT_MS)).isTrue();
         assertThat(mMicrodroidDevice.enableAdbRoot()).isTrue();
-
         return mMicrodroidDevice;
     }
+
+    // Try to launch protected non-debuggable VM for a while and quit.
+    // Non-debuggable VM doesn't enable adb, so there's no ITestDevice instance of it.
+    private CommandResult tryLaunchProtectedNonDebuggableVm() throws DeviceNotAvailableException {
+        // Can't use MicrodroidBuilder because it expects adb connection
+        // but non-debuggable VM doesn't enable adb.
+        CommandRunner runner = new CommandRunner(mAndroidDevice);
+        runner.run("mkdir", "-p", TEST_ROOT);
+        mAndroidDevice.pushFile(mCustomPvmfwBinFileOnHost, TEST_ROOT + PVMFW_FILE_NAME);
+
+        // This will fail because app wouldn't finish itself.
+        // But let's run the app once and get logs.
+        String command =
+                String.join(
+                        " ",
+                        "/apex/com.android.virt/bin/vm",
+                        "run-app",
+                        "--log",
+                        MICRODROID_LOG_PATH,
+                        "--protected",
+                        getPathForPackage(PACKAGE_NAME),
+                        TEST_ROOT + "idsig",
+                        TEST_ROOT + "instance.img",
+                        "--config-path",
+                        MICRODROID_CONFIG_PATH);
+        return mAndroidDevice.executeShellV2Command(
+                command, CONSOLE_OUTPUT_WAIT_MS, TimeUnit.MILLISECONDS, /* retryAttempts= */ 0);
+    }
 }