Merge changes from topic "snapshot_fuzz"

* changes:
  libsnapshot_fuzzer: Add tests
  libsnapshot_fuzzer: add initial corpus
  libsnapshot_fuzzer: Attempt to cleanup env before and after
  libsnapshot_fuzzer: add new test directive to switch slot
  libsnapshot_fuzzer: mount data image
  libsnapshot_fuzzer: also create snapshots dir
  libsnapshot_fuzzer: Add ZERO to operation types
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index 676f446..6cd0430 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -14,6 +14,9 @@
     },
     {
       "name": "vts_libsnapshot_test"
+    },
+    {
+      "name": "libsnapshot_fuzzer_test"
     }
   ]
 }
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index e916693..2783e4d 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -246,8 +246,8 @@
     gtest: false,
 }
 
-cc_fuzz {
-    name: "libsnapshot_fuzzer",
+cc_defaults {
+    name: "libsnapshot_fuzzer_defaults",
 
     // TODO(b/154633114): make host supported.
     // host_supported: true,
@@ -289,7 +289,12 @@
         canonical_path_from_root: false,
         local_include_dirs: ["."],
     },
+}
 
+cc_fuzz {
+    name: "libsnapshot_fuzzer",
+    defaults: ["libsnapshot_fuzzer_defaults"],
+    corpus: ["corpus/*"],
     fuzz_config: {
         cc: ["android-virtual-ab+bugs@google.com"],
         componentid: 30545,
@@ -298,3 +303,14 @@
         fuzz_on_haiku_device: true,
     },
 }
+
+cc_test {
+    name: "libsnapshot_fuzzer_test",
+    defaults: ["libsnapshot_fuzzer_defaults"],
+    data: ["corpus/*"],
+    test_suites: [
+        "device-tests",
+    ],
+    auto_gen_config: true,
+    require_root: true,
+}
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto
index 91fbb60..a55b42a 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto
@@ -64,6 +64,7 @@
         bool has_metadata_device_object = 1;
         bool metadata_mounted = 2;
     }
+    reserved 18 to 9999;
     oneof value {
         NoArgs begin_update = 1;
         NoArgs cancel_update = 2;
@@ -82,6 +83,9 @@
         NoArgs dump = 15;
         NoArgs ensure_metadata_mounted = 16;
         NoArgs get_snapshot_merge_stats_instance = 17;
+
+        // Test directives that has nothing to do with ISnapshotManager API surface.
+        NoArgs switch_slot = 10000;
     }
 }
 
@@ -97,7 +101,10 @@
     bool is_super_metadata_valid = 3;
     chromeos_update_engine.DeltaArchiveManifest super_data = 4;
 
+    // Whether the directory that mocks /metadata/ota/snapshot is created.
+    bool has_metadata_snapshots_dir = 5;
+
     // More data used to prep the test before running actions.
-    reserved 5 to 9999;
+    reserved 6 to 9999;
     repeated SnapshotManagerActionProto actions = 10000;
 }
diff --git a/fs_mgr/libsnapshot/corpus/launch_device.txt b/fs_mgr/libsnapshot/corpus/launch_device.txt
new file mode 100644
index 0000000..55a7f2c
--- /dev/null
+++ b/fs_mgr/libsnapshot/corpus/launch_device.txt
@@ -0,0 +1,161 @@
+device_info_data {
+  slot_suffix_is_a: true
+  is_overlayfs_setup: false
+  allow_set_boot_control_merge_status: true
+  allow_set_slot_as_unbootable: true
+  is_recovery: false
+}
+manager_data {
+  is_local_image_manager: false
+}
+is_super_metadata_valid: true
+super_data {
+  partitions {
+    partition_name: "sys_a"
+    new_partition_info {
+      size: 3145728
+    }
+  }
+  partitions {
+    partition_name: "vnd_a"
+    new_partition_info {
+      size: 3145728
+    }
+  }
+  partitions {
+    partition_name: "prd_a"
+    new_partition_info {
+      size: 3145728
+    }
+  }
+  dynamic_partition_metadata {
+    groups {
+      name: "group_google_dp_a"
+      size: 15728640
+      partition_names: "sys_a"
+      partition_names: "vnd_a"
+      partition_names: "prd_a"
+    }
+  }
+}
+has_metadata_snapshots_dir: true
+actions {
+  begin_update {
+  }
+}
+actions {
+  create_update_snapshots {
+    partitions {
+      partition_name: "sys"
+      new_partition_info {
+        size: 3878912
+      }
+      operations {
+        type: ZERO,
+        dst_extents {
+          start_block: 0
+          num_blocks: 947
+        }
+      }
+    }
+    partitions {
+      partition_name: "vnd"
+      new_partition_info {
+        size: 3878912
+      }
+      operations {
+        type: ZERO,
+        dst_extents {
+          start_block: 0
+          num_blocks: 947
+        }
+      }
+    }
+    partitions {
+      partition_name: "prd"
+      new_partition_info {
+        size: 3878912
+      }
+      operations {
+        type: ZERO,
+        dst_extents {
+          start_block: 0
+          num_blocks: 947
+        }
+      }
+    }
+    dynamic_partition_metadata {
+      groups {
+        name: "group_google_dp"
+        size: 15728640
+        partition_names: "sys"
+        partition_names: "vnd"
+        partition_names: "prd"
+      }
+    }
+  }
+}
+actions {
+  map_update_snapshot {
+    use_correct_super: true
+    has_metadata_slot: true
+    metadata_slot: 1
+    partition_name: "sys_b"
+    force_writable: true
+    timeout_millis: 3000
+  }
+}
+actions {
+  map_update_snapshot {
+    use_correct_super: true
+    has_metadata_slot: true
+    metadata_slot: 1
+    partition_name: "vnd_b"
+    force_writable: true
+    timeout_millis: 3000
+  }
+}
+actions {
+  map_update_snapshot {
+    use_correct_super: true
+    has_metadata_slot: true
+    metadata_slot: 1
+    partition_name: "prd_b"
+    force_writable: true
+    timeout_millis: 3000
+  }
+}
+actions {
+  finished_snapshot_writes: false
+}
+actions {
+  unmap_update_snapshot: "sys_b"
+}
+actions {
+  unmap_update_snapshot: "vnd_b"
+}
+actions {
+  unmap_update_snapshot: "prd_b"
+}
+actions {
+  switch_slot {
+  }
+}
+actions {
+  need_snapshots_in_first_stage_mount {
+  }
+}
+actions {
+  create_logical_and_snapshot_partitions {
+    use_correct_super: true
+    timeout_millis: 5000
+  }
+}
+actions {
+  initiate_merge {
+  }
+}
+actions {
+  process_update_state {
+  }
+}
diff --git a/fs_mgr/libsnapshot/fuzz.sh b/fs_mgr/libsnapshot/fuzz.sh
index 2910129..0e57674 100755
--- a/fs_mgr/libsnapshot/fuzz.sh
+++ b/fs_mgr/libsnapshot/fuzz.sh
@@ -3,7 +3,8 @@
 FUZZ_TARGET=libsnapshot_fuzzer
 TARGET_ARCH=$(get_build_var TARGET_ARCH)
 FUZZ_BINARY=/data/fuzz/${TARGET_ARCH}/${FUZZ_TARGET}/${FUZZ_TARGET}
-DEVICE_CORPSE_DIR=/data/local/tmp/${FUZZ_TARGET}
+DEVICE_INIT_CORPUS_DIR=/data/fuzz/${TARGET_ARCH}/${FUZZ_TARGET}/corpus
+DEVICE_GENERATED_CORPUS_DIR=/data/local/tmp/${FUZZ_TARGET}/corpus
 DEVICE_GCOV_DIR=/data/local/tmp/${FUZZ_TARGET}/gcov
 HOST_SCRATCH_DIR=/tmp/${FUZZ_TARGET}
 GCOV_TOOL=${HOST_SCRATCH_DIR}/llvm-gcov
@@ -26,13 +27,14 @@
 
 prepare_device() {
     adb root && adb remount &&
-    adb shell mkdir -p ${DEVICE_CORPSE_DIR} &&
+    adb shell mkdir -p ${DEVICE_GENERATED_CORPUS_DIR} &&
     adb shell rm -rf ${DEVICE_GCOV_DIR} &&
     adb shell mkdir -p ${DEVICE_GCOV_DIR}
 }
 
 push_binary() {
-    adb push ${ANDROID_PRODUCT_OUT}/${FUZZ_BINARY} ${FUZZ_BINARY}
+    adb push ${ANDROID_PRODUCT_OUT}/${FUZZ_BINARY} ${FUZZ_BINARY} &&
+    adb push ${ANDROID_PRODUCT_OUT}/${DEVICE_INIT_CORPUS_DIR} $(dirname ${FUZZ_BINARY})
 }
 
 prepare_host() {
@@ -52,7 +54,7 @@
     prepare_device &&
     build_normal &&
     push_binary &&
-    adb shell ${FUZZ_BINARY} "$@" ${DEVICE_CORPSE_DIR}
+    adb shell ${FUZZ_BINARY} "$@" ${DEVICE_INIT_CORPUS_DIR} ${DEVICE_GENERATED_CORPUS_DIR}
 }
 
 run_snapshot_fuzz() {
@@ -62,7 +64,7 @@
     adb shell GCOV_PREFIX=${DEVICE_GCOV_DIR} GCOV_PREFIX_STRIP=3 \
         ${FUZZ_BINARY} \
         -runs=0 \
-        ${DEVICE_CORPSE_DIR}
+        ${DEVICE_INIT_CORPUS_DIR} ${DEVICE_GENERATED_CORPUS_DIR}
 }
 
 show_fuzz_result() {
@@ -82,7 +84,7 @@
 
 # run_snapshot_fuzz -runs=10000
 run_snapshot_fuzz_all() {
-    generate_corpse "$@" &&
+    generate_corpus "$@" &&
     run_snapshot_fuzz &&
     show_fuzz_result
 }
diff --git a/fs_mgr/libsnapshot/fuzz_utils.h b/fs_mgr/libsnapshot/fuzz_utils.h
index 4dc6cdc..20b13b2 100644
--- a/fs_mgr/libsnapshot/fuzz_utils.h
+++ b/fs_mgr/libsnapshot/fuzz_utils.h
@@ -68,17 +68,25 @@
     return 0;
 }
 
+// Get the field descriptor for the oneof field in the action message. If no oneof field is set,
+// return nullptr.
 template <typename Action>
-void ExecuteActionProto(typename Action::Class* module,
-                        const typename Action::Proto& action_proto) {
+const google::protobuf::FieldDescriptor* GetValueFieldDescriptor(
+        const typename Action::Proto& action_proto) {
     static auto* action_value_desc = GetProtoValueDescriptor(Action::Proto::GetDescriptor());
 
     auto* action_refl = Action::Proto::GetReflection();
     if (!action_refl->HasOneof(action_proto, action_value_desc)) {
-        return;
+        return nullptr;
     }
+    return action_refl->GetOneofFieldDescriptor(action_proto, action_value_desc);
+}
 
-    const auto* field_desc = action_refl->GetOneofFieldDescriptor(action_proto, action_value_desc);
+template <typename Action>
+void ExecuteActionProto(typename Action::ClassType* module,
+                        const typename Action::Proto& action_proto) {
+    const auto* field_desc = GetValueFieldDescriptor<Action>(action_proto);
+    if (field_desc == nullptr) return;
     auto number = field_desc->number();
     const auto& map = *Action::GetFunctionMap();
     auto it = map.find(number);
@@ -89,7 +97,7 @@
 
 template <typename Action>
 void ExecuteAllActionProtos(
-        typename Action::Class* module,
+        typename Action::ClassType* module,
         const google::protobuf::RepeatedPtrField<typename Action::Proto>& action_protos) {
     for (const auto& proto : action_protos) {
         ExecuteActionProto<Action>(module, proto);
@@ -134,53 +142,57 @@
 // ActionPerformer extracts arguments from the protobuf message, and then call FuzzFunction
 // with these arguments.
 template <typename FuzzFunction, typename Signature, typename Enabled = void>
-struct ActionPerfomer;  // undefined
+struct ActionPerformerImpl;  // undefined
 
 template <typename FuzzFunction, typename MessageProto>
-struct ActionPerfomer<
+struct ActionPerformerImpl<
         FuzzFunction, void(const MessageProto&),
         typename std::enable_if_t<std::is_base_of_v<google::protobuf::Message, MessageProto>>> {
-    static void Invoke(typename FuzzFunction::Class* module,
-                       const google::protobuf::Message& action_proto,
-                       const google::protobuf::FieldDescriptor* field_desc) {
+    static typename FuzzFunction::ReturnType Invoke(
+            typename FuzzFunction::ClassType* module, const google::protobuf::Message& action_proto,
+            const google::protobuf::FieldDescriptor* field_desc) {
         const MessageProto& arg = CheckedCast<std::remove_reference_t<MessageProto>>(
                 action_proto.GetReflection()->GetMessage(action_proto, field_desc));
-        FuzzFunction::ImplBody(module, arg);
+        return FuzzFunction::ImplBody(module, arg);
     }
 };
 
 template <typename FuzzFunction, typename Primitive>
-struct ActionPerfomer<FuzzFunction, void(Primitive),
-                      typename std::enable_if_t<std::is_arithmetic_v<Primitive>>> {
-    static void Invoke(typename FuzzFunction::Class* module,
-                       const google::protobuf::Message& action_proto,
-                       const google::protobuf::FieldDescriptor* field_desc) {
+struct ActionPerformerImpl<FuzzFunction, void(Primitive),
+                           typename std::enable_if_t<std::is_arithmetic_v<Primitive>>> {
+    static typename FuzzFunction::ReturnType Invoke(
+            typename FuzzFunction::ClassType* module, const google::protobuf::Message& action_proto,
+            const google::protobuf::FieldDescriptor* field_desc) {
         Primitive arg = std::invoke(PrimitiveGetter<Primitive>::fp, action_proto.GetReflection(),
                                     action_proto, field_desc);
-        FuzzFunction::ImplBody(module, arg);
+        return FuzzFunction::ImplBody(module, arg);
     }
 };
 
 template <typename FuzzFunction>
-struct ActionPerfomer<FuzzFunction, void()> {
-    static void Invoke(typename FuzzFunction::Class* module, const google::protobuf::Message&,
-                       const google::protobuf::FieldDescriptor*) {
-        FuzzFunction::ImplBody(module);
+struct ActionPerformerImpl<FuzzFunction, void()> {
+    static typename FuzzFunction::ReturnType Invoke(typename FuzzFunction::ClassType* module,
+                                                    const google::protobuf::Message&,
+                                                    const google::protobuf::FieldDescriptor*) {
+        return FuzzFunction::ImplBody(module);
     }
 };
 
 template <typename FuzzFunction>
-struct ActionPerfomer<FuzzFunction, void(const std::string&)> {
-    static void Invoke(typename FuzzFunction::Class* module,
-                       const google::protobuf::Message& action_proto,
-                       const google::protobuf::FieldDescriptor* field_desc) {
+struct ActionPerformerImpl<FuzzFunction, void(const std::string&)> {
+    static typename FuzzFunction::ReturnType Invoke(
+            typename FuzzFunction::ClassType* module, const google::protobuf::Message& action_proto,
+            const google::protobuf::FieldDescriptor* field_desc) {
         std::string scratch;
         const std::string& arg = action_proto.GetReflection()->GetStringReference(
                 action_proto, field_desc, &scratch);
-        FuzzFunction::ImplBody(module, arg);
+        return FuzzFunction::ImplBody(module, arg);
     }
 };
 
+template <typename FuzzFunction>
+struct ActionPerformer : ActionPerformerImpl<FuzzFunction, typename FuzzFunction::Signature> {};
+
 }  // namespace android::fuzz
 
 // Fuzz existing C++ class, ClassType, with a collection of functions under the name Action.
@@ -197,11 +209,11 @@
 //   FUZZ_CLASS(Foo, FooAction)
 // After linking functions of Foo to FooAction, execute all actions by:
 //   FooAction::ExecuteAll(foo_object, action_protos)
-#define FUZZ_CLASS(ClassType, Action)                                                            \
+#define FUZZ_CLASS(Class, Action)                                                                \
     class Action {                                                                               \
       public:                                                                                    \
         using Proto = Action##Proto;                                                             \
-        using Class = ClassType;                                                                 \
+        using ClassType = Class;                                                                 \
         using FunctionMap = android::fuzz::FunctionMap<Class>;                                   \
         static FunctionMap* GetFunctionMap() {                                                   \
             static Action::FunctionMap map;                                                      \
@@ -225,29 +237,33 @@
 // }
 // class Foo { public: void DoAwesomeFoo(bool arg); };
 // FUZZ_OBJECT(FooAction, Foo);
-// FUZZ_FUNCTION(FooAction, DoFoo, module, bool arg) {
+// FUZZ_FUNCTION(FooAction, DoFoo, void, IFoo* module, bool arg) {
 //   module->DoAwesomeFoo(arg);
 // }
 // The name DoFoo is the camel case name of the action in protobuf definition of FooActionProto.
-#define FUZZ_FUNCTION(Action, FunctionName, module, ...)                                         \
-    class FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName) {                                       \
-      public:                                                                                    \
-        using Class = Action::Class;                                                             \
-        static void ImplBody(Action::Class*, ##__VA_ARGS__);                                     \
-                                                                                                 \
-      private:                                                                                   \
-        static bool registered_;                                                                 \
-    };                                                                                           \
-    auto FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::registered_ = ([] {                     \
-        auto tag = Action::Proto::ValueCase::FUZZ_FUNCTION_TAG_NAME(FunctionName);               \
-        auto func =                                                                              \
-                &::android::fuzz::ActionPerfomer<FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName), \
-                                                 void(__VA_ARGS__)>::Invoke;                     \
-        Action::GetFunctionMap()->CheckEmplace(tag, func);                                       \
-        return true;                                                                             \
-    })();                                                                                        \
-    void FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::ImplBody(Action::Class* module,         \
-                                                                  ##__VA_ARGS__)
+#define FUZZ_FUNCTION(Action, FunctionName, Return, ModuleArg, ...)             \
+    class FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName) {                      \
+      public:                                                                   \
+        using ActionType = Action;                                              \
+        using ClassType = Action::ClassType;                                    \
+        using ReturnType = Return;                                              \
+        using Signature = void(__VA_ARGS__);                                    \
+        static constexpr const char name[] = #FunctionName;                     \
+        static constexpr const auto tag =                                       \
+                Action::Proto::ValueCase::FUZZ_FUNCTION_TAG_NAME(FunctionName); \
+        static ReturnType ImplBody(ModuleArg, ##__VA_ARGS__);                   \
+                                                                                \
+      private:                                                                  \
+        static bool registered_;                                                \
+    };                                                                          \
+    auto FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::registered_ = ([] {    \
+        auto tag = FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::tag;         \
+        auto func = &::android::fuzz::ActionPerformer<FUZZ_FUNCTION_CLASS_NAME( \
+                Action, FunctionName)>::Invoke;                                 \
+        Action::GetFunctionMap()->CheckEmplace(tag, func);                      \
+        return true;                                                            \
+    })();                                                                       \
+    Return FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::ImplBody(ModuleArg, ##__VA_ARGS__)
 
 // Implement a simple action by linking it to the function with the same name. Example:
 // message FooActionProto {
@@ -261,5 +277,9 @@
 // FUZZ_FUNCTION(FooAction, DoBar);
 // The name DoBar is the camel case name of the action in protobuf definition of FooActionProto, and
 // also the name of the function of Foo.
-#define FUZZ_SIMPLE_FUNCTION(Action, FunctionName) \
-    FUZZ_FUNCTION(Action, FunctionName, module) { (void)module->FunctionName(); }
+#define FUZZ_SIMPLE_FUNCTION(Action, FunctionName)                            \
+    FUZZ_FUNCTION(Action, FunctionName,                                       \
+                  decltype(std::declval<Action::ClassType>().FunctionName()), \
+                  Action::ClassType* module) {                                \
+        return module->FunctionName();                                        \
+    }
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz.cpp b/fs_mgr/libsnapshot/snapshot_fuzz.cpp
index 421154d..5b145c3 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz.cpp
@@ -21,14 +21,21 @@
 #include <tuple>
 
 #include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/result.h>
+#include <gtest/gtest.h>
 #include <src/libfuzzer/libfuzzer_macro.h>
 #include <storage_literals/storage_literals.h>
 
 #include "fuzz_utils.h"
 #include "snapshot_fuzz_utils.h"
 
+using android::base::Error;
+using android::base::GetBoolProperty;
 using android::base::LogId;
 using android::base::LogSeverity;
+using android::base::ReadFileToString;
+using android::base::Result;
 using android::base::SetLogger;
 using android::base::StderrLogger;
 using android::base::StdioLogger;
@@ -37,6 +44,8 @@
 using android::snapshot::SnapshotFuzzData;
 using android::snapshot::SnapshotFuzzEnv;
 using chromeos_update_engine::DeltaArchiveManifest;
+using google::protobuf::FieldDescriptor;
+using google::protobuf::Message;
 using google::protobuf::RepeatedPtrField;
 
 // Avoid linking to libgsi since it needs disk I/O.
@@ -54,6 +63,7 @@
 namespace android::snapshot {
 
 const SnapshotFuzzData* current_data = nullptr;
+const SnapshotTestModule* current_module = nullptr;
 
 SnapshotFuzzEnv* GetSnapshotFuzzEnv();
 
@@ -73,48 +83,49 @@
 FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, EnsureMetadataMounted);
 FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, GetSnapshotMergeStatsInstance);
 
-#define SNAPSHOT_FUZZ_FUNCTION(FunctionName, ...) \
-    FUZZ_FUNCTION(SnapshotManagerAction, FunctionName, snapshot, ##__VA_ARGS__)
+#define SNAPSHOT_FUZZ_FUNCTION(FunctionName, ReturnType, ...)                                  \
+    FUZZ_FUNCTION(SnapshotManagerAction, FunctionName, ReturnType, ISnapshotManager* snapshot, \
+                  ##__VA_ARGS__)
 
-SNAPSHOT_FUZZ_FUNCTION(FinishedSnapshotWrites, bool wipe) {
-    (void)snapshot->FinishedSnapshotWrites(wipe);
+SNAPSHOT_FUZZ_FUNCTION(FinishedSnapshotWrites, bool, bool wipe) {
+    return snapshot->FinishedSnapshotWrites(wipe);
 }
 
-SNAPSHOT_FUZZ_FUNCTION(ProcessUpdateState, const ProcessUpdateStateArgs& args) {
+SNAPSHOT_FUZZ_FUNCTION(ProcessUpdateState, bool, const ProcessUpdateStateArgs& args) {
     std::function<bool()> before_cancel;
     if (args.has_before_cancel()) {
         before_cancel = [&]() { return args.fail_before_cancel(); };
     }
-    (void)snapshot->ProcessUpdateState({}, before_cancel);
+    return snapshot->ProcessUpdateState({}, before_cancel);
 }
 
-SNAPSHOT_FUZZ_FUNCTION(GetUpdateState, bool has_progress_arg) {
+SNAPSHOT_FUZZ_FUNCTION(GetUpdateState, UpdateState, bool has_progress_arg) {
     double progress;
-    (void)snapshot->GetUpdateState(has_progress_arg ? &progress : nullptr);
+    return snapshot->GetUpdateState(has_progress_arg ? &progress : nullptr);
 }
 
-SNAPSHOT_FUZZ_FUNCTION(HandleImminentDataWipe, bool has_callback) {
+SNAPSHOT_FUZZ_FUNCTION(HandleImminentDataWipe, bool, bool has_callback) {
     std::function<void()> callback;
     if (has_callback) {
         callback = []() {};
     }
-    (void)snapshot->HandleImminentDataWipe(callback);
+    return snapshot->HandleImminentDataWipe(callback);
 }
 
-SNAPSHOT_FUZZ_FUNCTION(Dump) {
+SNAPSHOT_FUZZ_FUNCTION(Dump, bool) {
     std::stringstream ss;
-    (void)snapshot->Dump(ss);
+    return snapshot->Dump(ss);
 }
 
-SNAPSHOT_FUZZ_FUNCTION(CreateUpdateSnapshots, const DeltaArchiveManifest& manifest) {
-    (void)snapshot->CreateUpdateSnapshots(manifest);
+SNAPSHOT_FUZZ_FUNCTION(CreateUpdateSnapshots, bool, const DeltaArchiveManifest& manifest) {
+    return snapshot->CreateUpdateSnapshots(manifest);
 }
 
-SNAPSHOT_FUZZ_FUNCTION(UnmapUpdateSnapshot, const std::string& name) {
-    (void)snapshot->UnmapUpdateSnapshot(name);
+SNAPSHOT_FUZZ_FUNCTION(UnmapUpdateSnapshot, bool, const std::string& name) {
+    return snapshot->UnmapUpdateSnapshot(name);
 }
 
-SNAPSHOT_FUZZ_FUNCTION(CreateLogicalAndSnapshotPartitions,
+SNAPSHOT_FUZZ_FUNCTION(CreateLogicalAndSnapshotPartitions, bool,
                        const CreateLogicalAndSnapshotPartitionsArgs& args) {
     const std::string* super;
     if (args.use_correct_super()) {
@@ -122,20 +133,21 @@
     } else {
         super = &args.super();
     }
-    (void)snapshot->CreateLogicalAndSnapshotPartitions(
+    return snapshot->CreateLogicalAndSnapshotPartitions(
             *super, std::chrono::milliseconds(args.timeout_millis()));
 }
 
-SNAPSHOT_FUZZ_FUNCTION(RecoveryCreateSnapshotDevicesWithMetadata,
+SNAPSHOT_FUZZ_FUNCTION(RecoveryCreateSnapshotDevicesWithMetadata, CreateResult,
                        const RecoveryCreateSnapshotDevicesArgs& args) {
     std::unique_ptr<AutoDevice> device;
     if (args.has_metadata_device_object()) {
         device = std::make_unique<DummyAutoDevice>(args.metadata_mounted());
     }
-    (void)snapshot->RecoveryCreateSnapshotDevices(device);
+    return snapshot->RecoveryCreateSnapshotDevices(device);
 }
 
-SNAPSHOT_FUZZ_FUNCTION(MapUpdateSnapshot, const CreateLogicalPartitionParamsProto& params_proto) {
+SNAPSHOT_FUZZ_FUNCTION(MapUpdateSnapshot, bool,
+                       const CreateLogicalPartitionParamsProto& params_proto) {
     auto partition_opener = std::make_unique<TestPartitionOpener>(GetSnapshotFuzzEnv()->super());
     CreateLogicalPartitionParams params;
     if (params_proto.use_correct_super()) {
@@ -152,7 +164,14 @@
     params.device_name = params_proto.device_name();
     params.partition_opener = partition_opener.get();
     std::string path;
-    (void)snapshot->MapUpdateSnapshot(params, &path);
+    return snapshot->MapUpdateSnapshot(params, &path);
+}
+
+SNAPSHOT_FUZZ_FUNCTION(SwitchSlot, void) {
+    (void)snapshot;
+    CHECK(current_module != nullptr);
+    CHECK(current_module->device_info != nullptr);
+    current_module->device_info->SwitchSlot();
 }
 
 // During global init, log all messages to stdio. This is only done once.
@@ -186,7 +205,8 @@
 }
 // Stop logging (except fatal messages) after global initialization. This is only done once.
 int StopLoggingAfterGlobalInit() {
-    [[maybe_unused]] static protobuf_mutator::protobuf::LogSilencer log_silincer;
+    (void)GetSnapshotFuzzEnv();
+    [[maybe_unused]] static protobuf_mutator::protobuf::LogSilencer log_silencer;
     SetLogger(&FatalOnlyLogger);
     return 0;
 }
@@ -194,22 +214,139 @@
 SnapshotFuzzEnv* GetSnapshotFuzzEnv() {
     [[maybe_unused]] static auto allow_logging = AllowLoggingDuringGlobalInit();
     static SnapshotFuzzEnv env;
-    [[maybe_unused]] static auto stop_logging = StopLoggingAfterGlobalInit();
     return &env;
 }
 
+SnapshotTestModule SetUpTest(const SnapshotFuzzData& snapshot_fuzz_data) {
+    current_data = &snapshot_fuzz_data;
+
+    auto env = GetSnapshotFuzzEnv();
+    env->CheckSoftReset();
+
+    auto test_module = env->CheckCreateSnapshotManager(snapshot_fuzz_data);
+    current_module = &test_module;
+    CHECK(test_module.snapshot);
+    return test_module;
+}
+
+void TearDownTest() {
+    current_module = nullptr;
+    current_data = nullptr;
+}
+
 }  // namespace android::snapshot
 
 DEFINE_PROTO_FUZZER(const SnapshotFuzzData& snapshot_fuzz_data) {
     using namespace android::snapshot;
 
-    current_data = &snapshot_fuzz_data;
-
-    auto env = GetSnapshotFuzzEnv();
-    env->CheckSoftReset();
-
-    auto snapshot_manager = env->CheckCreateSnapshotManager(snapshot_fuzz_data);
-    CHECK(snapshot_manager);
-
-    SnapshotManagerAction::ExecuteAll(snapshot_manager.get(), snapshot_fuzz_data.actions());
+    [[maybe_unused]] static auto stop_logging = StopLoggingAfterGlobalInit();
+    auto test_module = SetUpTest(snapshot_fuzz_data);
+    SnapshotManagerAction::ExecuteAll(test_module.snapshot.get(), snapshot_fuzz_data.actions());
+    TearDownTest();
 }
+
+namespace android::snapshot {
+
+// Work-around to cast a 'void' value to Result<void>.
+template <typename T>
+struct GoodResult {
+    template <typename F>
+    static Result<T> Cast(F&& f) {
+        return f();
+    }
+};
+
+template <>
+struct GoodResult<void> {
+    template <typename F>
+    static Result<void> Cast(F&& f) {
+        f();
+        return {};
+    }
+};
+
+class LibsnapshotFuzzerTest : public ::testing::Test {
+  protected:
+    static void SetUpTestCase() {
+        // Do initialization once.
+        (void)GetSnapshotFuzzEnv();
+    }
+    void SetUp() override {
+        bool is_virtual_ab = GetBoolProperty("ro.virtual_ab.enabled", false);
+        if (!is_virtual_ab) GTEST_SKIP() << "Test only runs on Virtual A/B devices.";
+    }
+    void SetUpFuzzData(const std::string& fn) {
+        auto path = android::base::GetExecutableDirectory() + "/corpus/"s + fn;
+        std::string proto_text;
+        ASSERT_TRUE(ReadFileToString(path, &proto_text));
+        snapshot_fuzz_data_ = std::make_unique<SnapshotFuzzData>();
+        ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(proto_text,
+                                                                  snapshot_fuzz_data_.get()));
+        test_module_ = android::snapshot::SetUpTest(*snapshot_fuzz_data_);
+    }
+    void TearDown() override { android::snapshot::TearDownTest(); }
+    template <typename FuzzFunction>
+    Result<typename FuzzFunction::ReturnType> Execute(int action_index) {
+        if (action_index >= snapshot_fuzz_data_->actions_size()) {
+            return Error() << "Index " << action_index << " is out of bounds ("
+                           << snapshot_fuzz_data_->actions_size() << " actions in corpus";
+        }
+        const auto& action_proto = snapshot_fuzz_data_->actions(action_index);
+        const auto* field_desc =
+                android::fuzz::GetValueFieldDescriptor<typename FuzzFunction::ActionType>(
+                        action_proto);
+        if (field_desc == nullptr) {
+            return Error() << "Action at index " << action_index << " has no value defined.";
+        }
+        if (FuzzFunction::tag != field_desc->number()) {
+            return Error() << "Action at index " << action_index << " is expected to be "
+                           << FuzzFunction::name << ", but it is " << field_desc->name()
+                           << " in corpus.";
+        }
+        return GoodResult<typename FuzzFunction::ReturnType>::Cast([&]() {
+            return android::fuzz::ActionPerformer<FuzzFunction>::Invoke(test_module_.snapshot.get(),
+                                                                        action_proto, field_desc);
+        });
+    }
+
+    std::unique_ptr<SnapshotFuzzData> snapshot_fuzz_data_;
+    SnapshotTestModule test_module_;
+};
+
+#define SNAPSHOT_FUZZ_FN_NAME(name) FUZZ_FUNCTION_CLASS_NAME(SnapshotManagerAction, name)
+
+MATCHER_P(ResultIs, expected, "") {
+    if (!arg.ok()) {
+        *result_listener << arg.error();
+        return false;
+    }
+    *result_listener << "expected: " << expected;
+    return arg.value() == expected;
+}
+
+#define ASSERT_RESULT_TRUE(actual) ASSERT_THAT(actual, ResultIs(true))
+
+// Check that launch_device.txt is executed correctly.
+TEST_F(LibsnapshotFuzzerTest, LaunchDevice) {
+    SetUpFuzzData("launch_device.txt");
+
+    int i = 0;
+    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(BeginUpdate)>(i++));
+    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(CreateUpdateSnapshots)>(i++));
+    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(MapUpdateSnapshot)>(i++)) << "sys_b";
+    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(MapUpdateSnapshot)>(i++)) << "vnd_b";
+    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(MapUpdateSnapshot)>(i++)) << "prd_b";
+    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(FinishedSnapshotWrites)>(i++));
+    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(UnmapUpdateSnapshot)>(i++)) << "sys_b";
+    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(UnmapUpdateSnapshot)>(i++)) << "vnd_b";
+    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(UnmapUpdateSnapshot)>(i++)) << "prd_b";
+    ASSERT_RESULT_OK(Execute<SNAPSHOT_FUZZ_FN_NAME(SwitchSlot)>(i++));
+    ASSERT_EQ("_b", test_module_.device_info->GetSlotSuffix());
+    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(NeedSnapshotsInFirstStageMount)>(i++));
+    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(CreateLogicalAndSnapshotPartitions)>(i++));
+    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(InitiateMerge)>(i++));
+    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(ProcessUpdateState)>(i++));
+    ASSERT_EQ(i, snapshot_fuzz_data_->actions_size()) << "Not all actions are executed.";
+}
+
+}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
index 8101d03..c9f1ab0 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
@@ -24,7 +24,11 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <cutils/properties.h>
+#include <fs_mgr.h>
 #include <libsnapshot/auto_device.h>
 #include <libsnapshot/snapshot.h>
 #include <storage_literals/storage_literals.h>
@@ -41,21 +45,30 @@
 using namespace std::chrono_literals;
 using namespace std::string_literals;
 
+using android::base::Basename;
+using android::base::ReadFileToString;
+using android::base::SetProperty;
+using android::base::Split;
+using android::base::StartsWith;
 using android::base::StringPrintf;
 using android::base::unique_fd;
 using android::base::WriteStringToFile;
+using android::dm::DeviceMapper;
+using android::dm::DmTarget;
 using android::dm::LoopControl;
 using android::fiemap::IImageManager;
 using android::fiemap::ImageManager;
 using android::fs_mgr::BlockDeviceInfo;
+using android::fs_mgr::FstabEntry;
 using android::fs_mgr::IPartitionOpener;
 using chromeos_update_engine::DynamicPartitionMetadata;
 
-// This directory is exempted from pinning in ImageManager.
-static const char MNT_DIR[] = "/data/gsi/ota/test/";
+static const char MNT_DIR[] = "/mnt";
+static const char BLOCK_SYSFS[] = "/sys/block";
 
 static const char FAKE_ROOT_NAME[] = "snapshot_fuzz";
 static const auto SUPER_IMAGE_SIZE = 16_MiB;
+static const auto DATA_IMAGE_SIZE = 16_MiB;
 static const auto FAKE_ROOT_SIZE = 64_MiB;
 
 namespace android::snapshot {
@@ -98,6 +111,149 @@
     return nftw(path.c_str(), callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) == 0;
 }
 
+std::string GetLinearBaseDeviceString(const DeviceMapper::TargetInfo& target) {
+    if (target.spec.target_type != "linear"s) return {};
+    auto tokens = Split(target.data, " ");
+    CHECK_EQ(2, tokens.size());
+    return tokens[0];
+}
+
+std::vector<std::string> GetSnapshotBaseDeviceStrings(const DeviceMapper::TargetInfo& target) {
+    if (target.spec.target_type != "snapshot"s && target.spec.target_type != "snapshot-merge"s)
+        return {};
+    auto tokens = Split(target.data, " ");
+    CHECK_EQ(4, tokens.size());
+    return {tokens[0], tokens[1]};
+}
+
+bool ShouldDeleteLoopDevice(const std::string& node) {
+    std::string backing_file;
+    if (ReadFileToString(StringPrintf("%s/loop/backing_file", node.data()), &backing_file)) {
+        if (StartsWith(backing_file, std::string(MNT_DIR) + "/" + FAKE_ROOT_NAME)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+std::vector<DeviceMapper::TargetInfo> GetTableInfoIfExists(const std::string& dev_name) {
+    auto& dm = DeviceMapper::Instance();
+    std::vector<DeviceMapper::TargetInfo> table;
+    if (!dm.GetTableInfo(dev_name, &table)) {
+        PCHECK(errno == ENODEV);
+        return {};
+    }
+    return table;
+}
+
+std::set<std::string> GetAllBaseDeviceStrings(const std::string& child_dev) {
+    std::set<std::string> ret;
+    for (const auto& child_target : GetTableInfoIfExists(child_dev)) {
+        auto snapshot_bases = GetSnapshotBaseDeviceStrings(child_target);
+        ret.insert(snapshot_bases.begin(), snapshot_bases.end());
+
+        auto linear_base = GetLinearBaseDeviceString(child_target);
+        if (!linear_base.empty()) {
+            ret.insert(linear_base);
+        }
+    }
+    return ret;
+}
+
+using PropertyList = std::set<std::string>;
+void InsertProperty(const char* key, const char* /*name*/, void* cookie) {
+    reinterpret_cast<PropertyList*>(cookie)->insert(key);
+}
+
+void CheckUnsetGsidProps() {
+    PropertyList list;
+    property_list(&InsertProperty, reinterpret_cast<void*>(&list));
+    for (const auto& key : list) {
+        SetProperty(key, "");
+    }
+}
+
+// Attempt to delete all devices that is based on dev_name, including itself.
+void CheckDeleteDeviceMapperTree(const std::string& dev_name, bool known_allow_delete = false,
+                                 uint64_t depth = 100) {
+    CHECK(depth > 0) << "Reaching max depth when deleting " << dev_name
+                     << ". There may be devices referencing itself. Check `dmctl list devices -v`.";
+
+    auto& dm = DeviceMapper::Instance();
+    auto table = GetTableInfoIfExists(dev_name);
+    if (table.empty()) {
+        PCHECK(dm.DeleteDeviceIfExists(dev_name)) << dev_name;
+        return;
+    }
+
+    if (!known_allow_delete) {
+        for (const auto& target : table) {
+            auto base_device_string = GetLinearBaseDeviceString(target);
+            if (base_device_string.empty()) continue;
+            if (ShouldDeleteLoopDevice(
+                        StringPrintf("/sys/dev/block/%s", base_device_string.data()))) {
+                known_allow_delete = true;
+                break;
+            }
+        }
+    }
+    if (!known_allow_delete) {
+        return;
+    }
+
+    std::string dev_string;
+    PCHECK(dm.GetDeviceString(dev_name, &dev_string));
+
+    std::vector<DeviceMapper::DmBlockDevice> devices;
+    PCHECK(dm.GetAvailableDevices(&devices));
+    for (const auto& child_dev : devices) {
+        auto child_bases = GetAllBaseDeviceStrings(child_dev.name());
+        if (child_bases.find(dev_string) != child_bases.end()) {
+            CheckDeleteDeviceMapperTree(child_dev.name(), true /* known_allow_delete */, depth - 1);
+        }
+    }
+
+    PCHECK(dm.DeleteDeviceIfExists(dev_name)) << dev_name;
+}
+
+// Attempt to clean up residues from previous runs.
+void CheckCleanupDeviceMapperDevices() {
+    auto& dm = DeviceMapper::Instance();
+    std::vector<DeviceMapper::DmBlockDevice> devices;
+    PCHECK(dm.GetAvailableDevices(&devices));
+
+    for (const auto& dev : devices) {
+        CheckDeleteDeviceMapperTree(dev.name());
+    }
+}
+
+void CheckUmount(const std::string& path) {
+    PCHECK(TEMP_FAILURE_RETRY(umount(path.data()) == 0) || errno == ENOENT || errno == EINVAL)
+            << path;
+}
+
+void CheckDetachLoopDevices(const std::set<std::string>& exclude_names = {}) {
+    // ~SnapshotFuzzEnv automatically does the following.
+    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(BLOCK_SYSFS), closedir);
+    PCHECK(dir != nullptr) << BLOCK_SYSFS;
+    LoopControl loop_control;
+    dirent* dp;
+    while ((dp = readdir(dir.get())) != nullptr) {
+        if (exclude_names.find(dp->d_name) != exclude_names.end()) {
+            continue;
+        }
+        if (!ShouldDeleteLoopDevice(StringPrintf("%s/%s", BLOCK_SYSFS, dp->d_name).data())) {
+            continue;
+        }
+        PCHECK(loop_control.Detach(StringPrintf("/dev/block/%s", dp->d_name).data()));
+    }
+}
+
+void CheckUmountAll() {
+    CheckUmount(std::string(MNT_DIR) + "/snapshot_fuzz_data");
+    CheckUmount(std::string(MNT_DIR) + "/" + FAKE_ROOT_NAME);
+}
+
 class AutoDeleteDir : public AutoDevice {
   public:
     static std::unique_ptr<AutoDeleteDir> New(const std::string& path) {
@@ -108,9 +264,7 @@
     }
     ~AutoDeleteDir() {
         if (!HasDevice()) return;
-        if (rmdir(name_.c_str()) == -1) {
-            PLOG(ERROR) << "Cannot remove " << name_;
-        }
+        PCHECK(rmdir(name_.c_str()) == 0 || errno == ENOENT) << name_;
     }
 
   private:
@@ -119,6 +273,15 @@
 
 class AutoUnmount : public AutoDevice {
   public:
+    ~AutoUnmount() {
+        if (!HasDevice()) return;
+        CheckUmount(name_);
+    }
+    AutoUnmount(const std::string& path) : AutoDevice(path) {}
+};
+
+class AutoUnmountTmpfs : public AutoUnmount {
+  public:
     static std::unique_ptr<AutoUnmount> New(const std::string& path, uint64_t size) {
         if (mount("tmpfs", path.c_str(), "tmpfs", 0,
                   (void*)StringPrintf("size=%" PRIu64, size).data()) == -1) {
@@ -127,30 +290,20 @@
         }
         return std::unique_ptr<AutoUnmount>(new AutoUnmount(path));
     }
-    ~AutoUnmount() {
-        if (!HasDevice()) return;
-        if (umount(name_.c_str()) == -1) {
-            PLOG(ERROR) << "Cannot umount " << name_;
-        }
-    }
-
   private:
-    AutoUnmount(const std::string& path) : AutoDevice(path) {}
+    using AutoUnmount::AutoUnmount;
 };
 
 // A directory on tmpfs. Upon destruct, it is unmounted and deleted.
 class AutoMemBasedDir : public AutoDevice {
   public:
     static std::unique_ptr<AutoMemBasedDir> New(const std::string& name, uint64_t size) {
-        if (!Mkdir(MNT_DIR)) {
-            return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
-        }
         auto ret = std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(name));
         ret->auto_delete_mount_dir_ = AutoDeleteDir::New(ret->mount_path());
         if (!ret->auto_delete_mount_dir_->HasDevice()) {
             return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
         }
-        ret->auto_umount_mount_point_ = AutoUnmount::New(ret->mount_path(), size);
+        ret->auto_umount_mount_point_ = AutoUnmountTmpfs::New(ret->mount_path(), size);
         if (!ret->auto_umount_mount_point_->HasDevice()) {
             return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
         }
@@ -191,14 +344,41 @@
 };
 
 SnapshotFuzzEnv::SnapshotFuzzEnv() {
+    CheckUnsetGsidProps();
+    CheckCleanupDeviceMapperDevices();
+    CheckDetachLoopDevices();
+    CheckUmountAll();
+
     fake_root_ = AutoMemBasedDir::New(FAKE_ROOT_NAME, FAKE_ROOT_SIZE);
     CHECK(fake_root_ != nullptr);
     CHECK(fake_root_->HasDevice());
     loop_control_ = std::make_unique<LoopControl>();
-    mapped_super_ = CheckMapSuper(fake_root_->persist_path(), loop_control_.get(), &fake_super_);
+
+    fake_data_mount_point_ = MNT_DIR + "/snapshot_fuzz_data"s;
+    auto_delete_data_mount_point_ = AutoDeleteDir::New(fake_data_mount_point_);
+    CHECK(auto_delete_data_mount_point_ != nullptr);
+    CHECK(auto_delete_data_mount_point_->HasDevice());
+
+    const auto& fake_persist_path = fake_root_->persist_path();
+    mapped_super_ = CheckMapImage(fake_persist_path + "/super.img", SUPER_IMAGE_SIZE,
+                                  loop_control_.get(), &fake_super_);
+    mapped_data_ = CheckMapImage(fake_persist_path + "/data.img", DATA_IMAGE_SIZE,
+                                 loop_control_.get(), &fake_data_block_device_);
+    mounted_data_ = CheckMountFormatData(fake_data_block_device_, fake_data_mount_point_);
 }
 
-SnapshotFuzzEnv::~SnapshotFuzzEnv() = default;
+SnapshotFuzzEnv::~SnapshotFuzzEnv() {
+    CheckUnsetGsidProps();
+    CheckCleanupDeviceMapperDevices();
+    mounted_data_ = nullptr;
+    auto_delete_data_mount_point_ = nullptr;
+    mapped_data_ = nullptr;
+    mapped_super_ = nullptr;
+    CheckDetachLoopDevices();
+    loop_control_ = nullptr;
+    fake_root_ = nullptr;
+    CheckUmountAll();
+}
 
 void CheckZeroFill(const std::string& file, size_t size) {
     std::string zeros(size, '\0');
@@ -208,15 +388,12 @@
 void SnapshotFuzzEnv::CheckSoftReset() {
     fake_root_->CheckSoftReset();
     CheckZeroFill(super(), SUPER_IMAGE_SIZE);
+    CheckCleanupDeviceMapperDevices();
+    CheckDetachLoopDevices({Basename(fake_super_), Basename(fake_data_block_device_)});
 }
 
 std::unique_ptr<IImageManager> SnapshotFuzzEnv::CheckCreateFakeImageManager(
-        const std::string& path) {
-    auto images_dir = path + "/images";
-    auto metadata_dir = images_dir + "/metadata";
-    auto data_dir = images_dir + "/data";
-
-    PCHECK(Mkdir(images_dir));
+        const std::string& metadata_dir, const std::string& data_dir) {
     PCHECK(Mkdir(metadata_dir));
     PCHECK(Mkdir(data_dir));
     return ImageManager::Open(metadata_dir, data_dir);
@@ -236,36 +413,42 @@
   public:
     AutoDetachLoopDevice(LoopControl* control, const std::string& device)
         : AutoDevice(device), control_(control) {}
-    ~AutoDetachLoopDevice() { control_->Detach(name_); }
+    ~AutoDetachLoopDevice() { PCHECK(control_->Detach(name_)) << name_; }
 
   private:
     LoopControl* control_;
 };
 
-std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMapSuper(const std::string& fake_persist_path,
-                                                           LoopControl* control,
-                                                           std::string* fake_super) {
-    auto super_img = fake_persist_path + "/super.img";
-    CheckZeroFill(super_img, SUPER_IMAGE_SIZE);
-    CheckCreateLoopDevice(control, super_img, 1s, fake_super);
+std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMapImage(const std::string& img_path,
+                                                           uint64_t size, LoopControl* control,
+                                                           std::string* mapped_path) {
+    CheckZeroFill(img_path, size);
+    CheckCreateLoopDevice(control, img_path, 1s, mapped_path);
 
-    return std::make_unique<AutoDetachLoopDevice>(control, *fake_super);
+    return std::make_unique<AutoDetachLoopDevice>(control, *mapped_path);
 }
 
-std::unique_ptr<ISnapshotManager> SnapshotFuzzEnv::CheckCreateSnapshotManager(
-        const SnapshotFuzzData& data) {
+SnapshotTestModule SnapshotFuzzEnv::CheckCreateSnapshotManager(const SnapshotFuzzData& data) {
+    SnapshotTestModule ret;
     auto partition_opener = std::make_unique<TestPartitionOpener>(super());
+    ret.opener = partition_opener.get();
     CheckWriteSuperMetadata(data, *partition_opener);
     auto metadata_dir = fake_root_->tmp_path() + "/snapshot_metadata";
     PCHECK(Mkdir(metadata_dir));
+    if (data.has_metadata_snapshots_dir()) {
+        PCHECK(Mkdir(metadata_dir + "/snapshots"));
+    }
 
-    auto device_info = new SnapshotFuzzDeviceInfo(data.device_info_data(),
-                                                  std::move(partition_opener), metadata_dir);
-    auto snapshot = SnapshotManager::New(device_info /* takes ownership */);
-    snapshot->images_ = CheckCreateFakeImageManager(fake_root_->tmp_path());
+    ret.device_info = new SnapshotFuzzDeviceInfo(data.device_info_data(),
+                                                 std::move(partition_opener), metadata_dir);
+    auto snapshot = SnapshotManager::New(ret.device_info /* takes ownership */);
+    snapshot->images_ =
+            CheckCreateFakeImageManager(fake_root_->tmp_path() + "/images_manager_metadata",
+                                        fake_data_mount_point_ + "/image_manager_data");
     snapshot->has_local_image_manager_ = data.manager_data().is_local_image_manager();
+    ret.snapshot = std::move(snapshot);
 
-    return snapshot;
+    return ret;
 }
 
 const std::string& SnapshotFuzzEnv::super() const {
@@ -311,4 +494,17 @@
     CHECK(FlashPartitionTable(opener, super(), *metadata.get()));
 }
 
+std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMountFormatData(const std::string& blk_device,
+                                                                  const std::string& mount_point) {
+    FstabEntry entry{
+            .blk_device = blk_device,
+            .length = static_cast<off64_t>(DATA_IMAGE_SIZE),
+            .fs_type = "ext4",
+            .mount_point = mount_point,
+    };
+    CHECK(0 == fs_mgr_do_format(entry, false /* crypt_footer */));
+    CHECK(0 == fs_mgr_do_mount_one(entry));
+    return std::make_unique<AutoUnmount>(mount_point);
+}
+
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
index 5533def..2405088 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
@@ -31,12 +31,19 @@
 namespace android::snapshot {
 
 class AutoMemBasedDir;
+class SnapshotFuzzDeviceInfo;
 
 class DummyAutoDevice : public AutoDevice {
   public:
     DummyAutoDevice(bool mounted) : AutoDevice(mounted ? "dummy" : "") {}
 };
 
+struct SnapshotTestModule {
+    std::unique_ptr<ISnapshotManager> snapshot;
+    SnapshotFuzzDeviceInfo* device_info = nullptr;
+    TestPartitionOpener* opener = nullptr;
+};
+
 // Prepare test environment. This has a heavy overhead and should be done once.
 class SnapshotFuzzEnv {
   public:
@@ -54,7 +61,7 @@
     // Create a snapshot manager for this test run.
     // Client is responsible for maintaining the lifetime of |data| over the life time of
     // ISnapshotManager.
-    std::unique_ptr<ISnapshotManager> CheckCreateSnapshotManager(const SnapshotFuzzData& data);
+    SnapshotTestModule CheckCreateSnapshotManager(const SnapshotFuzzData& data);
 
     // Return path to super partition.
     const std::string& super() const;
@@ -62,14 +69,22 @@
   private:
     std::unique_ptr<AutoMemBasedDir> fake_root_;
     std::unique_ptr<android::dm::LoopControl> loop_control_;
+    std::string fake_data_mount_point_;
+    std::unique_ptr<AutoDevice> auto_delete_data_mount_point_;
     std::unique_ptr<AutoDevice> mapped_super_;
     std::string fake_super_;
+    std::unique_ptr<AutoDevice> mapped_data_;
+    std::string fake_data_block_device_;
+    std::unique_ptr<AutoDevice> mounted_data_;
 
     static std::unique_ptr<android::fiemap::IImageManager> CheckCreateFakeImageManager(
-            const std::string& fake_tmp_path);
-    static std::unique_ptr<AutoDevice> CheckMapSuper(const std::string& fake_persist_path,
+            const std::string& metadata_dir, const std::string& data_dir);
+    static std::unique_ptr<AutoDevice> CheckMapImage(const std::string& fake_persist_path,
+                                                     uint64_t size,
                                                      android::dm::LoopControl* control,
-                                                     std::string* fake_super);
+                                                     std::string* mapped_path);
+    static std::unique_ptr<AutoDevice> CheckMountFormatData(const std::string& blk_device,
+                                                            const std::string& mount_point);
 
     void CheckWriteSuperMetadata(const SnapshotFuzzData& proto,
                                  const android::fs_mgr::IPartitionOpener& opener);
@@ -97,10 +112,8 @@
     }
 
     // Following APIs are fuzzed.
-    std::string GetSlotSuffix() const override { return data_->slot_suffix_is_a() ? "_a" : "_b"; }
-    std::string GetOtherSlotSuffix() const override {
-        return data_->slot_suffix_is_a() ? "_b" : "_a";
-    }
+    std::string GetSlotSuffix() const override { return CurrentSlotIsA() ? "_a" : "_b"; }
+    std::string GetOtherSlotSuffix() const override { return CurrentSlotIsA() ? "_b" : "_a"; }
     bool IsOverlayfsSetup() const override { return data_->is_overlayfs_setup(); }
     bool SetBootControlMergeStatus(android::hardware::boot::V1_1::MergeStatus) override {
         return data_->allow_set_boot_control_merge_status();
@@ -110,10 +123,15 @@
     }
     bool IsRecovery() const override { return data_->is_recovery(); }
 
+    void SwitchSlot() { switched_slot_ = !switched_slot_; }
+
   private:
     const FuzzDeviceInfoData* data_;
     std::unique_ptr<TestPartitionOpener> partition_opener_;
     std::string metadata_dir_;
+    bool switched_slot_ = false;
+
+    bool CurrentSlotIsA() const { return data_->slot_suffix_is_a() != switched_slot_; }
 };
 
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/update_engine/update_metadata.proto b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
index be5e1fe..8a11eaa 100644
--- a/fs_mgr/libsnapshot/update_engine/update_metadata.proto
+++ b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
@@ -45,7 +45,12 @@
 }
 
 message InstallOperation {
-    enum Type { SOURCE_COPY = 4; }
+    enum Type {
+        SOURCE_COPY = 4;
+        // Not used by libsnapshot. Declared here so that the fuzzer has an
+        // alternative value to use for |type|.
+        ZERO = 6;
+    }
     required Type type = 1;
     repeated Extent src_extents = 4;
     repeated Extent dst_extents = 6;