Add erofs filesystem unittest

Test: th
Bug: 206729162

Change-Id: I228cba4f1b5d40f164ee2e9df3575a836950bb38
diff --git a/Android.bp b/Android.bp
index 2a6b2a4..618b5fb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -737,6 +737,21 @@
     ],
 }
 
+genrule {
+    name: "ue_unittest_erofs_imgs",
+    cmd: "$(in) $(location mkfs.erofs) $(location gen/erofs_empty.img) &&" +
+         "$(in) $(location mkfs.erofs) $(location gen/erofs.img) $(location delta_generator)",
+    srcs: ["sample_images/generate_test_erofs_images.sh"],
+    out: [
+        "gen/erofs.img",
+        "gen/erofs_empty.img",
+    ],
+    tools: [
+        "mkfs.erofs",
+        "delta_generator",
+    ],
+}
+
 filegroup {
     name: "update_engine_host_unittest_srcs",
     srcs: [
@@ -760,6 +775,7 @@
         "payload_generator/boot_img_filesystem_unittest.cc",
         "payload_generator/deflate_utils_unittest.cc",
         "payload_generator/delta_diff_utils_unittest.cc",
+        "payload_generator/erofs_filesystem_unittest.cc",
         "payload_generator/ext2_filesystem_unittest.cc",
         "payload_generator/extent_ranges_unittest.cc",
         "payload_generator/extent_utils_unittest.cc",
@@ -794,6 +810,7 @@
     data: [
         ":ue_unittest_delta_generator",
         ":ue_unittest_disk_imgs",
+        ":ue_unittest_erofs_imgs",
         ":ue_unittest_keys",
         "otacerts.zip",
         "unittest_key.pem",
@@ -838,6 +855,7 @@
         ":test_subprocess",
         ":ue_unittest_delta_generator",
         ":ue_unittest_disk_imgs",
+        ":ue_unittest_erofs_imgs",
         ":ue_unittest_keys",
         "otacerts.zip",
         "unittest_key.pem",
diff --git a/payload_generator/erofs_filesystem_unittest.cc b/payload_generator/erofs_filesystem_unittest.cc
new file mode 100644
index 0000000..e6a8929
--- /dev/null
+++ b/payload_generator/erofs_filesystem_unittest.cc
@@ -0,0 +1,127 @@
+//
+// Copyright (C) 2021 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.
+//
+
+#include "update_engine/payload_generator/erofs_filesystem.h"
+
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include "payload_generator/delta_diff_generator.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/extent_utils.h"
+
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace {
+
+class ErofsFilesystemTest : public ::testing::Test {};
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+using test_utils::GetBuildArtifactsPath;
+
+TEST_F(ErofsFilesystemTest, InvalidFilesystem) {
+  ScopedTempFile fs_filename_{"ErofsFilesystemTest-XXXXXX"};
+  ASSERT_EQ(0, truncate(fs_filename_.path().c_str(), kBlockSize));
+  unique_ptr<ErofsFilesystem> fs =
+      ErofsFilesystem::CreateFromFile(fs_filename_.path());
+  ASSERT_EQ(nullptr, fs.get());
+
+  fs = ErofsFilesystem::CreateFromFile("/path/to/invalid/file");
+  ASSERT_EQ(nullptr, fs.get());
+}
+
+TEST_F(ErofsFilesystemTest, EmptyFilesystem) {
+  unique_ptr<ErofsFilesystem> fs = ErofsFilesystem::CreateFromFile(
+      GetBuildArtifactsPath("gen/erofs_empty.img"));
+
+  ASSERT_NE(nullptr, fs);
+  ASSERT_EQ(kBlockSize, fs->GetBlockSize());
+
+  vector<FilesystemInterface::File> files;
+  ASSERT_TRUE(fs->GetFiles(&files));
+  ASSERT_EQ(files.size(), 0UL);
+}
+
+// This test parses the sample images generated during build time with the
+// "generate_image.sh" script. The expected conditions of each file in these
+// images is encoded in the file name, as defined in the mentioned script.
+TEST_F(ErofsFilesystemTest, ParseGeneratedImages) {
+  const auto build_path = GetBuildArtifactsPath("gen/erofs.img");
+  auto fs = ErofsFilesystem::CreateFromFile(build_path);
+  ASSERT_NE(fs, nullptr);
+  ASSERT_EQ(kBlockSize, fs->GetBlockSize());
+
+  vector<ErofsFilesystem::File> files;
+  ASSERT_TRUE(fs->GetFiles(&files));
+
+  std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) {
+    return a.name < b.name;
+  });
+  vector<string> filenames;
+  filenames.resize(files.size());
+  std::transform(
+      files.begin(), files.end(), filenames.begin(), [](const auto& file) {
+        return file.name;
+      });
+  const std::vector<std::string> expected_filenames = {
+      "/delta_generator",
+      "/dir1/dir2/dir123/chunks_of_zero",
+      // Empty files are ignored
+      // "/dir1/dir2/dir123/empty",
+      "/dir1/dir2/file0",
+      "/dir1/dir2/file1",
+      "/dir1/dir2/file2",
+      "/dir1/dir2/file4",
+      "/dir1/file0",
+      "/dir1/file2",
+      "/file1",
+      // Files < 4K are stored inline, and therefore ignored, as they are often
+      // stored not on block boundary.
+      // "/generate_test_erofs_images.sh"
+  };
+  ASSERT_EQ(filenames, expected_filenames);
+  const auto delta_generator = files[0];
+  ASSERT_GT(delta_generator.compressed_file_info.blocks.size(), 0UL);
+  size_t compressed_size = 0;
+  size_t uncompressed_size = 0;
+  for (const auto& block : delta_generator.compressed_file_info.blocks) {
+    compressed_size += block.compressed_length;
+    uncompressed_size += block.uncompressed_length;
+  }
+  ASSERT_GE(uncompressed_size,
+            static_cast<size_t>(delta_generator.file_stat.st_size))
+      << "Uncompressed data should be at least as big as original file, plus "
+         "possible trailing data.";
+  const auto total_blocks = utils::BlocksInExtents(delta_generator.extents);
+  ASSERT_EQ(compressed_size, total_blocks * kBlockSize);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/ext2_filesystem_unittest.cc b/payload_generator/ext2_filesystem_unittest.cc
index 88e1538..8fa5080 100644
--- a/payload_generator/ext2_filesystem_unittest.cc
+++ b/payload_generator/ext2_filesystem_unittest.cc
@@ -57,10 +57,10 @@
   }
 }
 
-}  // namespace
-
 class Ext2FilesystemTest : public ::testing::Test {};
 
+}  // namespace
+
 TEST_F(Ext2FilesystemTest, InvalidFilesystem) {
   ScopedTempFile fs_filename_{"Ext2FilesystemTest-XXXXXX"};
   ASSERT_EQ(0, truncate(fs_filename_.path().c_str(), kDefaultFilesystemSize));
diff --git a/sample_images/generate_test_erofs_images.sh b/sample_images/generate_test_erofs_images.sh
new file mode 100755
index 0000000..c058f3c
--- /dev/null
+++ b/sample_images/generate_test_erofs_images.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+set -e
+
+sh_path=$0
+mkfs=$1
+output_image=$2
+delta_generator=$3
+
+fs_root=$(mktemp -d -t erofs-XXXXXXXXXX)
+
+clean_up () {
+    ARG=$?
+    rm -rf $fs_root
+    echo "> clean_up"
+    exit $ARG
+}
+trap clean_up EXIT
+
+if [ ! -z "${delta_generator}" ]; then
+  mkdir -p ${fs_root}/dir1/dir2/dir123/nested_dir
+  mkdir -p ${fs_root}/etc/
+  cp ${sh_path} ${fs_root}/
+  truncate -s 1M ${fs_root}/file1
+  truncate -s 1M ${fs_root}/dir1/file2
+  truncate -s 1M ${fs_root}/dir1/file0
+  truncate -s 1M ${fs_root}/dir1/dir2/file0
+  truncate -s 1M ${fs_root}/dir1/dir2/file1
+  truncate -s 1M ${fs_root}/dir1/dir2/file2
+  truncate -s 1M ${fs_root}/dir1/dir2/file4
+  touch ${fs_root}/dir1/dir2/dir123/empty
+  cp ${delta_generator} ${fs_root}/delta_generator
+  echo "PAYLOAD_MINOR_VERSION=1234" > ${fs_root}/etc/update_engine.conf
+  truncate -s 16M ${fs_root}/dir1/dir2/dir123/chunks_of_zero
+fi
+
+${mkfs} -z lz4hc,9 ${output_image} ${fs_root}