Add tests for get, create, getOrCreate

We didn't have any test coverage for getOrCreate, which is bad.

We also didn't have tests for the important property that you always
get the same VirtualMachine instance from these functions; nor did we
document that.

Also some light refactoring.

Test: atest MicrodroidTests
Bug: 261089825
Bug: 261037705
Change-Id: I84bdb4427c20ae774e1d0dc887cf67a241cddb89
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index ea0a305..5b30617 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -150,6 +150,11 @@
      * Returns an existing {@link VirtualMachine} with the given name. Returns null if there is no
      * such virtual machine.
      *
+     * <p>There is at most one {@code VirtualMachine} object corresponding to a given virtual
+     * machine instance. Multiple calls to get() passing the same name will get the same object
+     * returned, until the virtual machine is deleted (via {@link #delete}) and then recreated.
+     *
+     * @see #getOrCreate
      * @throws VirtualMachineException if the virtual machine exists but could not be successfully
      *     retrieved.
      * @hide
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 5f24c4b..eb756d8 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -258,14 +258,7 @@
         assertThrowsVmExceptionContaining(() -> vm.toDescriptor(), "deleted");
         // This is indistinguishable from the VM having never existed, so the message
         // is non-specific.
-        assertThrows(
-                VirtualMachineException.class, () -> getVirtualMachineManager().delete("test_vm"));
-    }
-
-    private void assertThrowsVmExceptionContaining(
-            ThrowingRunnable runnable, String expectedContents) {
-        Exception e = assertThrows(VirtualMachineException.class, runnable);
-        assertThat(e).hasMessageThat().contains(expectedContents);
+        assertThrowsVmException(() -> getVirtualMachineManager().delete("test_vm"));
     }
 
     @Test
@@ -423,6 +416,48 @@
 
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
+    public void vmmGetAndCreate() throws Exception {
+        assumeSupportedKernel();
+
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setMemoryMib(minMemoryRequired())
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+
+        VirtualMachineManager vmm = getVirtualMachineManager();
+        String vmName = "vmName";
+
+        // VM does not yet exist
+        assertThat(vmm.get(vmName)).isNull();
+
+        VirtualMachine vm1 = vmm.create(vmName, config);
+
+        // Now it does, and we should get the same instance back
+        assertThat(vmm.get(vmName)).isSameInstanceAs(vm1);
+        assertThat(vmm.getOrCreate(vmName, config)).isSameInstanceAs(vm1);
+
+        // Can't recreate it though
+        assertThrowsVmException(() -> vmm.create(vmName, config));
+
+        vmm.delete(vmName);
+        assertThat(vmm.get(vmName)).isNull();
+
+        // Now that we deleted the old one, this should create rather than get, and it should be a
+        // new instance.
+        VirtualMachine vm2 = vmm.getOrCreate(vmName, config);
+        assertThat(vm2).isNotSameInstanceAs(vm1);
+
+        // Subsequent gets should return this new one.
+        assertThat(vmm.get(vmName)).isSameInstanceAs(vm2);
+        assertThat(vmm.getOrCreate(vmName, config)).isSameInstanceAs(vm2);
+
+        vmm.delete(vmName);
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1"})
     public void vmFilesStoredInDeDirWhenCreatedFromDEContext() throws Exception {
         final Context ctx = getContext().createDeviceProtectedStorageContext();
         final int userId = ctx.getUserId();
@@ -511,10 +546,10 @@
         assertThat(vmm.get("test_vm_delete")).isNull();
 
         // Can't start the VM even with an existing reference
-        assertThrows(VirtualMachineException.class, vm::run);
+        assertThrowsVmException(vm::run);
 
         // Can't delete the VM since it no longer exists
-        assertThrows(VirtualMachineException.class, () -> vmm.delete("test_vm_delete"));
+        assertThrowsVmException(() -> vmm.delete("test_vm_delete"));
     }
 
     @Test
@@ -1098,6 +1133,16 @@
         return filePath.toFile();
     }
 
+    private void assertThrowsVmException(ThrowingRunnable runnable) {
+        assertThrows(VirtualMachineException.class, runnable);
+    }
+
+    private void assertThrowsVmExceptionContaining(
+            ThrowingRunnable runnable, String expectedContents) {
+        Exception e = assertThrows(VirtualMachineException.class, runnable);
+        assertThat(e).hasMessageThat().contains(expectedContents);
+    }
+
     private int minMemoryRequired() {
         if (Build.SUPPORTED_ABIS.length > 0) {
             String primaryAbi = Build.SUPPORTED_ABIS[0];