Add test to run vmbase example VM.

Bug: 223166344
Test: atest vmbase_example.integration_test
Change-Id: Idef13afb2466a920c1ead9626c81e7a36f6f67c5
diff --git a/vmbase/example/Android.bp b/vmbase/example/Android.bp
index 9c19693..0acef2b 100644
--- a/vmbase/example/Android.bp
+++ b/vmbase/example/Android.bp
@@ -61,3 +61,29 @@
         },
     },
 }
+
+rust_test {
+    name: "vmbase_example.integration_test",
+    crate_name: "vmbase_example_test",
+    srcs: ["tests/test.rs"],
+    prefer_rlib: true,
+    edition: "2021",
+    rustlibs: [
+        "android.system.virtualizationservice-rust",
+        "libanyhow",
+        "libenv_logger",
+        "liblibc",
+        "liblog_rust",
+        "libvmclient",
+    ],
+    data: [
+        ":vmbase_example",
+    ],
+    test_suites: ["general-tests"],
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+}
diff --git a/vmbase/example/tests/test.rs b/vmbase/example/tests/test.rs
new file mode 100644
index 0000000..4928846
--- /dev/null
+++ b/vmbase/example/tests/test.rs
@@ -0,0 +1,91 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Integration test for VM bootloader.
+
+use android_system_virtualizationservice::{
+    aidl::android::system::virtualizationservice::{
+        VirtualMachineConfig::VirtualMachineConfig,
+        VirtualMachineRawConfig::VirtualMachineRawConfig,
+    },
+    binder::{ParcelFileDescriptor, ProcessState},
+};
+use anyhow::{Context, Error};
+use log::info;
+use std::{
+    fs::File,
+    io,
+    os::unix::io::{AsRawFd, FromRawFd},
+};
+use vmclient::{DeathReason, VmInstance};
+
+const VMBASE_EXAMPLE_PATH: &str =
+    "/data/local/tmp/vmbase_example.integration_test/arm64/vmbase_example.bin";
+
+/// Runs the vmbase_example VM as an unprotected VM via VirtualizationService.
+#[test]
+fn test_run_example_vm() -> Result<(), Error> {
+    env_logger::init();
+
+    // We need to start the thread pool for Binder to work properly, especially link_to_death.
+    ProcessState::start_thread_pool();
+
+    let service = vmclient::connect().context("Failed to find VirtualizationService")?;
+
+    // Start example VM.
+    let bootloader = ParcelFileDescriptor::new(
+        File::open(VMBASE_EXAMPLE_PATH)
+            .with_context(|| format!("Failed to open VM image {}", VMBASE_EXAMPLE_PATH))?,
+    );
+    let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
+        kernel: None,
+        initrd: None,
+        params: None,
+        bootloader: Some(bootloader),
+        disks: vec![],
+        protectedVm: false,
+        memoryMib: 300,
+        numCpus: 1,
+        cpuAffinity: None,
+        platformVersion: "~1.0".to_string(),
+        taskProfiles: vec![],
+    });
+    let console = duplicate_stdout()?;
+    let log = duplicate_stdout()?;
+    let vm = VmInstance::create(service.as_ref(), &config, Some(console), Some(log))
+        .context("Failed to create VM")?;
+    vm.start().context("Failed to start VM")?;
+    info!("Started example VM.");
+
+    // Wait for VM to finish, and check that it shut down cleanly.
+    let death_reason = vm.wait_for_death();
+    assert_eq!(death_reason, DeathReason::Shutdown);
+
+    Ok(())
+}
+
+/// Safely duplicate the standard output file descriptor.
+fn duplicate_stdout() -> io::Result<File> {
+    let stdout_fd = io::stdout().as_raw_fd();
+    // Safe because this just duplicates a file descriptor which we know to be valid, and we check
+    // for an error.
+    let dup_fd = unsafe { libc::dup(stdout_fd) };
+    if dup_fd < 0 {
+        Err(io::Error::last_os_error())
+    } else {
+        // Safe because we have just duplicated the file descriptor so we own it, and `from_raw_fd`
+        // takes ownership of it.
+        Ok(unsafe { File::from_raw_fd(dup_fd) })
+    }
+}