When testing CompOS, write somewhere harmless

The motivation for this is mostly to allow us to run this on real
devices in teamfood without worrying that it will break things. It's
also preparation for extending composd to do more useful things as
well as running tests.

Modify ComposTestCase to always write to /test instead of
/dalvik-cache (both inside & outside the VM).

Improve the support for multiple instances in composd, rename the
force-compile method to make it clear it does test things, and make
sure it uses a test instance of composd (so odsign will ignore it).

Modify composd_cmd to take a parameter telling it what to do.

Plus some gratuitous tweaks / reformats.

Bug: 186126194
Bug: 200020887
Test: atest ComposTestCase
Test: adb shell apex/com.android.compos/bin/composd_cmd forced-compile-test
Change-Id: I7899fe6393b556e04d9e7f8a07671d96e72bb018
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index 104b8e5..0b84a28 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -33,11 +33,15 @@
 
 /// The sub-directory where we store information relating to the pending instance
 /// of CompOS (based on staged APEXes).
-pub const PENDING_DIR: &str = "pending";
+pub const PENDING_INSTANCE_DIR: &str = "pending";
 
 /// The sub-directory where we store information relating to the current instance
 /// of CompOS (based on active APEXes).
-pub const CURRENT_DIR: &str = "current";
+pub const CURRENT_INSTANCE_DIR: &str = "current";
+
+/// The sub-directory where we store information relating to the instance of CompOS used for
+/// tests.
+pub const TEST_INSTANCE_DIR: &str = "test";
 
 /// The file that holds the encrypted private key for a CompOS instance.
 pub const PRIVATE_KEY_BLOB_FILE: &str = "key.blob";
diff --git a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
index a1bb92c..3d0ad31 100644
--- a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
+++ b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
@@ -19,8 +19,13 @@
 import com.android.compos.FdAnnotation;
 
 interface IIsolatedCompilationService {
-    /** Run "odrefresh --force-compile" in CompOS. */
-    void runForcedCompile();
+    /**
+     * Run "odrefresh --dalvik-cache=pending-test --force-compile" in a test instance of CompOS.
+     * This compiles BCP extensions and system server, even if the system artifacts are up to date,
+     * and writes the results to a test directory to avoid disrupting any real artifacts in
+     * existence.
+     */
+    void runForcedCompileForTest();
 
     /**
      * Run dex2oat in the currently running instance of the CompOS VM. This is a simple proxy
diff --git a/compos/composd/src/instance_manager.rs b/compos/composd/src/instance_manager.rs
index 6b36ed8..e31296d 100644
--- a/compos/composd/src/instance_manager.rs
+++ b/compos/composd/src/instance_manager.rs
@@ -22,7 +22,7 @@
 use anyhow::{bail, Context, Result};
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
 use compos_aidl_interface::binder::Strong;
-use compos_common::CURRENT_DIR;
+use compos_common::{CURRENT_INSTANCE_DIR, TEST_INSTANCE_DIR};
 use std::sync::{Arc, Mutex, Weak};
 use virtualizationservice::IVirtualizationService::IVirtualizationService;
 
@@ -42,13 +42,22 @@
         Ok(instance.get_service())
     }
 
+    #[allow(dead_code)] // TODO: Make use of this
     pub fn start_current_instance(&self) -> Result<Arc<CompOsInstance>> {
+        self.start_instance(CURRENT_INSTANCE_DIR)
+    }
+
+    pub fn start_test_instance(&self) -> Result<Arc<CompOsInstance>> {
+        self.start_instance(TEST_INSTANCE_DIR)
+    }
+
+    fn start_instance(&self, instance_name: &str) -> Result<Arc<CompOsInstance>> {
         let mut state = self.state.lock().unwrap();
         state.mark_starting()?;
         // Don't hold the lock while we start the instance to avoid blocking other callers.
         drop(state);
 
-        let instance = self.try_start_current_instance();
+        let instance = self.try_start_instance(instance_name);
 
         let mut state = self.state.lock().unwrap();
         if let Ok(ref instance) = instance {
@@ -59,8 +68,8 @@
         instance
     }
 
-    fn try_start_current_instance(&self) -> Result<Arc<CompOsInstance>> {
-        let instance_starter = InstanceStarter::new(CURRENT_DIR);
+    fn try_start_instance(&self, instance_name: &str) -> Result<Arc<CompOsInstance>> {
+        let instance_starter = InstanceStarter::new(instance_name);
         let compos_instance = instance_starter.create_or_start_instance(&*self.service)?;
 
         Ok(Arc::new(compos_instance))
diff --git a/compos/composd/src/odrefresh.rs b/compos/composd/src/odrefresh.rs
index 2d880e2..2dfc3a1 100644
--- a/compos/composd/src/odrefresh.rs
+++ b/compos/composd/src/odrefresh.rs
@@ -37,10 +37,11 @@
     CleanupFailed = EX_MAX + 4,
 }
 
-pub fn run_forced_compile() -> Result<ExitCode> {
+pub fn run_forced_compile(target_dir: &str) -> Result<ExitCode> {
     // We don`t need to capture stdout/stderr - odrefresh writes to the log
     let mut odrefresh = Command::new(ODREFRESH_BIN)
         .arg(format!("--use-compilation-os={}", VMADDR_CID_ANY as i32))
+        .arg(format!("--dalvik-cache={}", target_dir))
         .arg("--force-compile")
         .spawn()
         .context("Running odrefresh")?;
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index be9c30c..d3b73a1 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -42,9 +42,9 @@
 impl Interface for IsolatedCompilationService {}
 
 impl IIsolatedCompilationService for IsolatedCompilationService {
-    fn runForcedCompile(&self) -> binder::Result<()> {
+    fn runForcedCompileForTest(&self) -> binder::Result<()> {
         // TODO - check caller is system or shell/root?
-        to_binder_result(self.do_run_forced_compile())
+        to_binder_result(self.do_run_forced_compile_for_test())
     }
 
     fn compile_cmd(
@@ -70,12 +70,12 @@
 }
 
 impl IsolatedCompilationService {
-    fn do_run_forced_compile(&self) -> Result<()> {
-        info!("runForcedCompile");
+    fn do_run_forced_compile_for_test(&self) -> Result<()> {
+        info!("runForcedCompileForTest");
 
-        let comp_os = self.instance_manager.start_current_instance().context("Starting CompOS")?;
+        let comp_os = self.instance_manager.start_test_instance().context("Starting CompOS")?;
 
-        let exit_code = odrefresh::run_forced_compile()?;
+        let exit_code = odrefresh::run_forced_compile("test-artifacts")?;
 
         if exit_code != odrefresh::ExitCode::CompilationSuccess {
             bail!("Unexpected odrefresh result: {:?}", exit_code);
diff --git a/compos/composd_cmd/Android.bp b/compos/composd_cmd/Android.bp
index 5fef86f..0081a0d 100644
--- a/compos/composd_cmd/Android.bp
+++ b/compos/composd_cmd/Android.bp
@@ -10,6 +10,7 @@
         "android.system.composd-rust",
         "libanyhow",
         "libbinder_rs",
+        "libclap",
     ],
     prefer_rlib: true,
     apex_available: [
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index e4884e3..04398c0 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -23,12 +23,25 @@
 use anyhow::{Context, Result};
 
 fn main() -> Result<()> {
+    let app = clap::App::new("composd_cmd").arg(
+        clap::Arg::with_name("command")
+            .index(1)
+            .takes_value(true)
+            .required(true)
+            .possible_values(&["forced-compile-test"]),
+    );
+    let args = app.get_matches();
+    let command = args.value_of("command").unwrap();
+
     ProcessState::start_thread_pool();
 
     let service = wait_for_interface::<dyn IIsolatedCompilationService>("android.system.composd")
         .context("Failed to connect to composd service")?;
 
-    service.runForcedCompile().context("Compilation failed")?;
+    match command {
+        "forced-compile-test" => service.runForcedCompileForTest().context("Compilation failed")?,
+        _ => panic!("Unexpected command {}", command),
+    }
 
     println!("All Ok!");
 
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 81e78ab..ba8d970 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -37,12 +37,13 @@
 
     // Binaries used in test. (These paths are valid both in host and Microdroid.)
     private static final String ODREFRESH_BIN = "/apex/com.android.art/bin/odrefresh";
-    private static final String COMPOS_KEY_CMD_BIN = "/apex/com.android.compos/bin/compos_key_cmd";
     private static final String COMPOSD_CMD_BIN = "/apex/com.android.compos/bin/composd_cmd";
 
     /** Output directory of odrefresh */
+    private static final String TEST_ARTIFACTS_DIR = "test-artifacts";
+
     private static final String ODREFRESH_OUTPUT_DIR =
-            "/data/misc/apexdata/com.android.art/dalvik-cache";
+            "/data/misc/apexdata/com.android.art/" + TEST_ARTIFACTS_DIR;
 
     /** Timeout of odrefresh to finish */
     private static final int ODREFRESH_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
@@ -51,12 +52,8 @@
     private static final int OKAY = 0;
     private static final int COMPILATION_SUCCESS = 80;
 
-    // Files that define the "current" instance of CompOS
-    private static final String COMPOS_CURRENT_ROOT =
-            "/data/misc/apexdata/com.android.compos/current/";
-    private static final String INSTANCE_IMAGE = COMPOS_CURRENT_ROOT + "instance.img";
-    private static final String PUBLIC_KEY = COMPOS_CURRENT_ROOT + "key.pubkey";
-    private static final String PRIVATE_KEY_BLOB = COMPOS_CURRENT_ROOT + "key.blob";
+    // Files that define the "test" instance of CompOS
+    private static final String COMPOS_TEST_ROOT = "/data/misc/apexdata/com.android.compos/test/";
 
     @Before
     public void setUp() throws Exception {
@@ -70,7 +67,7 @@
         CommandRunner android = new CommandRunner(getDevice());
 
         // Clear up any CompOS instance files we created
-        android.tryRun("rm", "-rf", COMPOS_CURRENT_ROOT);
+        android.tryRun("rm", "-rf", COMPOS_TEST_ROOT);
 
         // And any artifacts generated by odrefresh
         android.tryRun("rm", "-rf", ODREFRESH_OUTPUT_DIR);
@@ -83,9 +80,7 @@
         // Prepare the groundtruth. The compilation on Android should finish successfully.
         {
             long start = System.currentTimeMillis();
-            CommandResult result =
-                    android.runForResultWithTimeout(
-                            ODREFRESH_TIMEOUT_MS, ODREFRESH_BIN, "--force-compile");
+            CommandResult result = runOdrefresh(android, "--force-compile");
             long elapsed = System.currentTimeMillis() - start;
             assertThat(result.getExitCode()).isEqualTo(COMPILATION_SUCCESS);
             CLog.i("Local compilation took " + elapsed + "ms");
@@ -95,17 +90,18 @@
         String expectedChecksumSnapshot = checksumDirectoryContent(android, ODREFRESH_OUTPUT_DIR);
 
         // Let --check clean up the output.
-        CommandResult result =
-                android.runForResultWithTimeout(ODREFRESH_TIMEOUT_MS, ODREFRESH_BIN, "--check");
+        CommandResult result = runOdrefresh(android, "--check");
         assertThat(result.getExitCode()).isEqualTo(OKAY);
 
         // Make sure we generate a fresh instance
-        android.tryRun("rm", "-rf", COMPOS_CURRENT_ROOT);
+        android.tryRun("rm", "-rf", COMPOS_TEST_ROOT);
 
         // Expect the compilation in Compilation OS to finish successfully.
         {
             long start = System.currentTimeMillis();
-            result = android.runForResultWithTimeout(ODREFRESH_TIMEOUT_MS, COMPOSD_CMD_BIN);
+            result =
+                    android.runForResultWithTimeout(
+                            ODREFRESH_TIMEOUT_MS, COMPOSD_CMD_BIN, "forced-compile-test");
             long elapsed = System.currentTimeMillis() - start;
             assertThat(result.getExitCode()).isEqualTo(0);
             CLog.i("Comp OS compilation took " + elapsed + "ms");
@@ -116,16 +112,24 @@
         String actualChecksumSnapshot = checksumDirectoryContent(android, ODREFRESH_OUTPUT_DIR);
 
         // Expect the output to be valid.
-        result = android.runForResultWithTimeout(ODREFRESH_TIMEOUT_MS, ODREFRESH_BIN, "--verify");
+        result = runOdrefresh(android, "--verify");
         assertThat(result.getExitCode()).isEqualTo(OKAY);
         // --check can delete the output, so run later.
-        result = android.runForResultWithTimeout(ODREFRESH_TIMEOUT_MS, ODREFRESH_BIN, "--check");
+        result = runOdrefresh(android, "--check");
         assertThat(result.getExitCode()).isEqualTo(OKAY);
 
         // Expect the output of Comp OS to be the same as compiled on Android.
         assertThat(actualChecksumSnapshot).isEqualTo(expectedChecksumSnapshot);
     }
 
+    private CommandResult runOdrefresh(CommandRunner android, String command) throws Exception {
+        return android.runForResultWithTimeout(
+                ODREFRESH_TIMEOUT_MS,
+                ODREFRESH_BIN,
+                "--dalvik-cache=" + TEST_ARTIFACTS_DIR,
+                command);
+    }
+
     private void killVmAndReconnectAdb() throws Exception {
         CommandRunner android = new CommandRunner(getDevice());
 
diff --git a/compos/verify_key/verify_key.rs b/compos/verify_key/verify_key.rs
index 0cc6473..a5b0b6b 100644
--- a/compos/verify_key/verify_key.rs
+++ b/compos/verify_key/verify_key.rs
@@ -21,8 +21,8 @@
 use compos_aidl_interface::binder::ProcessState;
 use compos_common::compos_client::VmInstance;
 use compos_common::{
-    COMPOS_DATA_ROOT, CURRENT_DIR, INSTANCE_IMAGE_FILE, PENDING_DIR, PRIVATE_KEY_BLOB_FILE,
-    PUBLIC_KEY_FILE,
+    COMPOS_DATA_ROOT, CURRENT_INSTANCE_DIR, INSTANCE_IMAGE_FILE, PENDING_INSTANCE_DIR,
+    PRIVATE_KEY_BLOB_FILE, PUBLIC_KEY_FILE,
 };
 use std::fs::{self, File};
 use std::io::Read;
@@ -49,7 +49,9 @@
     let do_pending = matches.value_of("instance").unwrap() == "pending";
 
     let instance_dir: PathBuf =
-        [COMPOS_DATA_ROOT, if do_pending { PENDING_DIR } else { CURRENT_DIR }].iter().collect();
+        [COMPOS_DATA_ROOT, if do_pending { PENDING_INSTANCE_DIR } else { CURRENT_INSTANCE_DIR }]
+            .iter()
+            .collect();
 
     if !instance_dir.is_dir() {
         bail!("{} is not a directory", instance_dir.display());
@@ -112,7 +114,7 @@
 }
 
 fn promote_to_current(instance_dir: &Path) -> Result<()> {
-    let current_dir: PathBuf = [COMPOS_DATA_ROOT, CURRENT_DIR].iter().collect();
+    let current_dir: PathBuf = [COMPOS_DATA_ROOT, CURRENT_INSTANCE_DIR].iter().collect();
 
     // This may fail if the directory doesn't exist - which is fine, we only care about the rename
     // succeeding.
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index 5b7de58..5e83611 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -259,7 +259,7 @@
         }
     }
 
-    public static void rootMicrodroid() throws DeviceNotAvailableException {
+    public static void rootMicrodroid() {
         runOnHost("adb", "-s", MICRODROID_SERIAL, "root");
 
         // TODO(192660959): Figure out the root cause and remove the sleep. For unknown reason,
@@ -280,8 +280,7 @@
 
     // Establish an adb connection to microdroid by letting Android forward the connection to
     // microdroid. Wait until the connection is established and microdroid is booted.
-    public static void adbConnectToMicrodroid(ITestDevice androidDevice, String cid)
-            throws DeviceNotAvailableException {
+    public static void adbConnectToMicrodroid(ITestDevice androidDevice, String cid) {
         long start = System.currentTimeMillis();
         long timeoutMillis = MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000;
         long elapsed = 0;