Show error messages about the expected local image dir

In local-image-local-instance mode, if the user specifies
--local-system-image, --local-image should be an extracted target files
zip. Otherwise, it should be an extracted img zip.
If acloud cannot find the needed files in the image directory, it shows
an error message about the expected directory.

Test: acloud-dev create --local-instance --local-image ~/target_files
Test: acloud-dev create --local-instance --local-image ~/img \
      --local-system-image ~/gsi
Bug: 241521572
Change-Id: I1432b44913e5c348b6ea797a50733bcffe7e40c6
diff --git a/create/local_image_local_instance.py b/create/local_image_local_instance.py
index 61c2116..15bf685 100644
--- a/create/local_image_local_instance.py
+++ b/create/local_image_local_instance.py
@@ -78,6 +78,7 @@
 _MISC_INFO_FILE_NAME = "misc_info.txt"
 _TARGET_FILES_IMAGES_DIR_NAME = "IMAGES"
 _TARGET_FILES_META_DIR_NAME = "META"
+_SUPER_IMAGE_NAME = "super.img"
 _MIXED_SUPER_IMAGE_NAME = "mixed_super.img"
 _CMD_CVD_START = " start"
 _CMD_LAUNCH_CVD_ARGS = (
@@ -402,7 +403,9 @@
         if os.path.isfile(misc_info_path):
             return misc_info_path
         raise errors.CheckPathError(
-            "Cannot find %s in %s." % (_MISC_INFO_FILE_NAME, image_dir))
+            f"Cannot find {_MISC_INFO_FILE_NAME} in {image_dir}. The "
+            f"directory is expected to be an extracted target files zip or "
+            f"{constants.ENV_ANDROID_PRODUCT_OUT}.")
 
     @staticmethod
     def FindImageDir(image_dir):
@@ -428,6 +431,27 @@
             "Cannot find images in %s." % image_dir)
 
     @staticmethod
+    def _VerifyExtractedImgZip(image_dir):
+        """Verify that a path is build output dir or extracted img zip.
+
+        This method checks existence of super image. The file is in img zip
+        but not in target files zip. A cuttlefish instance requires a super
+        image if no system image or OTA tools are given.
+
+        Args:
+            image_dir: The directory to be verified.
+
+        Raises:
+            errors.GetLocalImageError if the directory does not contain the
+            needed file.
+        """
+        if not os.path.isfile(os.path.join(image_dir, _SUPER_IMAGE_NAME)):
+            raise errors.GetLocalImageError(
+                f"Cannot find {_SUPER_IMAGE_NAME} in {image_dir}. The "
+                f"directory is expected to be an extracted img zip or "
+                f"{constants.ENV_ANDROID_PRODUCT_OUT}.")
+
+    @staticmethod
     def _FindBootOrKernelImages(image_path):
         """Find boot, vendor_boot, kernel, and initramfs images in a path.
 
@@ -498,6 +522,7 @@
             system_image_path = create_common.FindLocalImage(
                 avd_spec.local_system_image, _SYSTEM_IMAGE_NAME_PATTERN)
         else:
+            self._VerifyExtractedImgZip(image_dir)
             misc_info_path = None
             ota_tools_dir = None
             system_image_path = None
diff --git a/create/local_image_local_instance_test.py b/create/local_image_local_instance_test.py
index 069b752..e7e5bdd 100644
--- a/create/local_image_local_instance_test.py
+++ b/create/local_image_local_instance_test.py
@@ -319,7 +319,7 @@
     def testGetImageArtifactsPath(self, mock_ota_tools):
         """Test GetImageArtifactsPath without system image dir."""
         with tempfile.TemporaryDirectory() as temp_dir:
-            image_dir = "/unit/test"
+            image_dir = os.path.join(temp_dir, "image")
             cvd_dir = os.path.join(temp_dir, "cvd-host_package")
             self._CreateEmptyFile(os.path.join(cvd_dir, "bin", "launch_cvd"))
             self._CreateEmptyFile(os.path.join(cvd_dir, "usr/share/webrtc/certs", "server.crt"))
@@ -330,6 +330,15 @@
                 local_system_image=None,
                 local_tool_dirs=[cvd_dir])
 
+            with self.assertRaisesRegex(
+                    errors.GetLocalImageError,
+                    r"The directory is expected to be an extracted img zip "
+                    r"or ANDROID_PRODUCT_OUT\."):
+                self.local_image_local_instance.GetImageArtifactsPath(
+                    mock_avd_spec)
+
+            self._CreateEmptyFile(os.path.join(image_dir, "super.img"))
+
             paths = self.local_image_local_instance.GetImageArtifactsPath(
                 mock_avd_spec)
 
@@ -399,14 +408,12 @@
             image_dir = os.path.join(temp_dir, "image")
             cvd_dir = os.path.join(temp_dir, "cvd-host_package")
             system_image_path = os.path.join(temp_dir, "system", "test.img")
-            misc_info_path = os.path.join(image_dir, "META", "misc_info.txt")
 
             self._CreateEmptyFile(os.path.join(image_dir, "IMAGES",
                                                "vbmeta.img"))
             self._CreateEmptyFile(os.path.join(cvd_dir, "bin", "launch_cvd"))
             self._CreateEmptyFile(os.path.join(cvd_dir, "usr/share/webrtc/certs", "server.crt"))
             self._CreateEmptyFile(system_image_path)
-            self._CreateEmptyFile(misc_info_path)
 
             mock_avd_spec = mock.Mock(
                 local_image_dir=image_dir,
@@ -414,6 +421,16 @@
                 local_system_image=system_image_path,
                 local_tool_dirs=[ota_tools_dir, cvd_dir])
 
+            with self.assertRaisesRegex(
+                    errors.CheckPathError,
+                    r"The directory is expected to be an extracted target "
+                    r"files zip or ANDROID_PRODUCT_OUT."):
+                self.local_image_local_instance.GetImageArtifactsPath(
+                    mock_avd_spec)
+
+            misc_info_path = os.path.join(image_dir, "META", "misc_info.txt")
+            self._CreateEmptyFile(misc_info_path)
+
             with mock.patch.dict("acloud.create.local_image_local_instance."
                                  "os.environ",
                                  clear=True):