Merge "Add unit tests for simulator"
This commit is contained in:
commit
4955648c4c
4 changed files with 414 additions and 10 deletions
|
@ -157,26 +157,22 @@ cc_test_host {
|
|||
|
||||
defaults: [
|
||||
"recovery_test_defaults",
|
||||
"libupdater_defaults",
|
||||
],
|
||||
|
||||
srcs: [
|
||||
"unit/imgdiff_test.cpp",
|
||||
"unit/host/*",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"libupdater_host",
|
||||
"libupdater_core",
|
||||
"libimgdiff",
|
||||
"libimgpatch",
|
||||
"libotautil",
|
||||
"libbsdiff",
|
||||
"libbspatch",
|
||||
"libziparchive",
|
||||
"libutils",
|
||||
"libcrypto",
|
||||
"libbrotli",
|
||||
"libbz",
|
||||
"libdivsufsort64",
|
||||
"libdivsufsort",
|
||||
"libz",
|
||||
"libfstab",
|
||||
"libc++fs",
|
||||
],
|
||||
|
||||
test_suites: ["general-tests"],
|
||||
|
|
403
tests/unit/host/update_simulator_test.cpp
Normal file
403
tests/unit/host/update_simulator_test.cpp
Normal file
|
@ -0,0 +1,403 @@
|
|||
/*
|
||||
* Copyright (C) 2019 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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <bsdiff/bsdiff.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <ziparchive/zip_writer.h>
|
||||
|
||||
#include "otautil/paths.h"
|
||||
#include "otautil/print_sha1.h"
|
||||
#include "updater/blockimg.h"
|
||||
#include "updater/build_info.h"
|
||||
#include "updater/install.h"
|
||||
#include "updater/simulator_runtime.h"
|
||||
#include "updater/target_files.h"
|
||||
#include "updater/updater.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
// echo -n "system.img" > system.img && img2simg system.img sparse_system_string_.img 4096 &&
|
||||
// hexdump -v -e '" " 12/1 "0x%02x, " "\n"' sparse_system_string_.img
|
||||
// The total size of the result sparse image is 4136 bytes; and we can append 0s in the end to get
|
||||
// the full image.
|
||||
constexpr uint8_t SPARSE_SYSTEM_HEADER[] = {
|
||||
0x3a, 0xff, 0x26, 0xed, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x10, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0xca,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x10, 0x00, 0x00, 0x73, 0x79, 0x73, 0x74, 0x65,
|
||||
0x6d, 0x2e, 0x69, 0x6d, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static void AddZipEntries(int fd, const std::map<string, string>& entries) {
|
||||
FILE* zip_file = fdopen(fd, "w");
|
||||
ZipWriter writer(zip_file);
|
||||
for (const auto& pair : entries) {
|
||||
ASSERT_EQ(0, writer.StartEntry(pair.first.c_str(), 0));
|
||||
ASSERT_EQ(0, writer.WriteBytes(pair.second.data(), pair.second.size()));
|
||||
ASSERT_EQ(0, writer.FinishEntry());
|
||||
}
|
||||
ASSERT_EQ(0, writer.Finish());
|
||||
ASSERT_EQ(0, fclose(zip_file));
|
||||
}
|
||||
|
||||
static string CalculateSha1(const string& data) {
|
||||
uint8_t digest[SHA_DIGEST_LENGTH];
|
||||
SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest);
|
||||
return print_sha1(digest);
|
||||
}
|
||||
|
||||
static void CreateBsdiffPatch(const string& src, const string& tgt, string* patch) {
|
||||
TemporaryFile patch_file;
|
||||
ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src.data()), src.size(),
|
||||
reinterpret_cast<const uint8_t*>(tgt.data()), tgt.size(),
|
||||
patch_file.path, nullptr));
|
||||
ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, patch));
|
||||
}
|
||||
|
||||
static void RunSimulation(std::string_view src_tf, std::string_view ota_package, bool expected) {
|
||||
TemporaryFile cmd_pipe;
|
||||
TemporaryFile temp_saved_source;
|
||||
TemporaryFile temp_last_command;
|
||||
TemporaryDir temp_stash_base;
|
||||
|
||||
Paths::Get().set_cache_temp_source(temp_saved_source.path);
|
||||
Paths::Get().set_last_command_file(temp_last_command.path);
|
||||
Paths::Get().set_stash_directory_base(temp_stash_base.path);
|
||||
|
||||
// Configure edify's functions.
|
||||
RegisterBuiltins();
|
||||
RegisterInstallFunctions();
|
||||
RegisterBlockImageFunctions();
|
||||
|
||||
// Run the update simulation and check the result.
|
||||
TemporaryDir work_dir;
|
||||
BuildInfo build_info(work_dir.path);
|
||||
ASSERT_TRUE(build_info.ParseTargetFile(src_tf, false));
|
||||
Updater updater(std::make_unique<SimulatorRuntime>(&build_info));
|
||||
ASSERT_TRUE(updater.Init(cmd_pipe.release(), ota_package, false));
|
||||
ASSERT_EQ(expected, updater.RunUpdate());
|
||||
// TODO(xunchang) check the recovery&system has the expected contents.
|
||||
}
|
||||
|
||||
class UpdateSimulatorTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
std::vector<string> props = {
|
||||
"import /oem/oem.prop oem*",
|
||||
"# begin build properties",
|
||||
"# autogenerated by buildinfo.sh",
|
||||
"ro.build.id=OPR1.170510.001",
|
||||
"ro.build.display.id=OPR1.170510.001 dev-keys",
|
||||
"ro.build.version.incremental=3993052",
|
||||
"ro.build.version.release=O",
|
||||
"ro.build.date=Wed May 10 11:10:29 UTC 2017",
|
||||
"ro.build.date.utc=1494414629",
|
||||
"ro.build.type=user",
|
||||
"ro.build.tags=dev-keys",
|
||||
"ro.build.flavor=angler-user",
|
||||
"ro.product.system.brand=google",
|
||||
"ro.product.system.name=angler",
|
||||
"ro.product.system.device=angler",
|
||||
};
|
||||
build_prop_string_ = android::base::Join(props, "\n");
|
||||
|
||||
fstab_content_ = R"(
|
||||
#<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
|
||||
# More comments.....
|
||||
|
||||
/dev/block/by-name/system /system ext4 ro,barrier=1 wait
|
||||
/dev/block/by-name/vendor /vendor ext4 ro wait,verify=/dev/metadata
|
||||
/dev/block/by-name/cache /cache ext4 noatime,errors=panic wait,check
|
||||
/dev/block/by-name/modem /firmware vfat ro,uid=1000,gid=1000, wait
|
||||
/dev/block/by-name/boot /boot emmc defaults defaults
|
||||
/dev/block/by-name/recovery /recovery emmc defaults defaults
|
||||
/dev/block/by-name/misc /misc emmc defaults
|
||||
/dev/block/by-name/modem /modem emmc defaults defaults)";
|
||||
|
||||
raw_system_string_ = "system.img" + string(4086, '\0'); // raw image is 4096 bytes in total
|
||||
sparse_system_string_ = string(SPARSE_SYSTEM_HEADER, std::end(SPARSE_SYSTEM_HEADER)) +
|
||||
string(4136 - sizeof(SPARSE_SYSTEM_HEADER), '\0');
|
||||
}
|
||||
|
||||
string build_prop_string_;
|
||||
string fstab_content_;
|
||||
string raw_system_string_;
|
||||
string sparse_system_string_;
|
||||
};
|
||||
|
||||
TEST_F(UpdateSimulatorTest, TargetFile_ExtractImage) {
|
||||
TemporaryFile zip_file;
|
||||
AddZipEntries(zip_file.release(), { { "META/misc_info.txt", "extfs_sparse_flag=-s" },
|
||||
{ "IMAGES/system.img", sparse_system_string_ } });
|
||||
TargetFile target_file(zip_file.path, false);
|
||||
ASSERT_TRUE(target_file.Open());
|
||||
|
||||
TemporaryDir temp_dir;
|
||||
TemporaryFile raw_image;
|
||||
ASSERT_TRUE(target_file.ExtractImage(
|
||||
"IMAGES/system.img", FstabInfo("/dev/system", "system", "ext4"), temp_dir.path, &raw_image));
|
||||
|
||||
// Check the raw image has expected contents.
|
||||
string content;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(raw_image.path, &content));
|
||||
string expected_content = "system.img" + string(4086, '\0');
|
||||
ASSERT_EQ(expected_content, content);
|
||||
}
|
||||
|
||||
TEST_F(UpdateSimulatorTest, TargetFile_ParseFstabInfo) {
|
||||
TemporaryFile zip_file;
|
||||
AddZipEntries(zip_file.release(),
|
||||
{ { "META/misc_info.txt", "" },
|
||||
{ "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ } });
|
||||
TargetFile target_file(zip_file.path, false);
|
||||
ASSERT_TRUE(target_file.Open());
|
||||
|
||||
std::vector<FstabInfo> fstab_info;
|
||||
EXPECT_TRUE(target_file.ParseFstabInfo(&fstab_info));
|
||||
|
||||
std::vector<std::vector<string>> transformed;
|
||||
std::transform(fstab_info.begin(), fstab_info.end(), std::back_inserter(transformed),
|
||||
[](const FstabInfo& info) {
|
||||
return std::vector<string>{ info.blockdev_name, info.mount_point, info.fs_type };
|
||||
});
|
||||
|
||||
std::vector<std::vector<string>> expected = {
|
||||
{ "/dev/block/by-name/system", "/system", "ext4" },
|
||||
{ "/dev/block/by-name/vendor", "/vendor", "ext4" },
|
||||
{ "/dev/block/by-name/cache", "/cache", "ext4" },
|
||||
{ "/dev/block/by-name/boot", "/boot", "emmc" },
|
||||
{ "/dev/block/by-name/recovery", "/recovery", "emmc" },
|
||||
{ "/dev/block/by-name/misc", "/misc", "emmc" },
|
||||
{ "/dev/block/by-name/modem", "/modem", "emmc" },
|
||||
};
|
||||
EXPECT_EQ(expected, transformed);
|
||||
}
|
||||
|
||||
TEST_F(UpdateSimulatorTest, BuildInfo_ParseTargetFile) {
|
||||
std::map<string, string> entries = {
|
||||
{ "META/misc_info.txt", "" },
|
||||
{ "SYSTEM/build.prop", build_prop_string_ },
|
||||
{ "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ },
|
||||
{ "IMAGES/recovery.img", "" },
|
||||
{ "IMAGES/boot.img", "" },
|
||||
{ "IMAGES/misc.img", "" },
|
||||
{ "IMAGES/system.map", "" },
|
||||
{ "IMAGES/system.img", sparse_system_string_ },
|
||||
};
|
||||
|
||||
TemporaryFile zip_file;
|
||||
AddZipEntries(zip_file.release(), entries);
|
||||
|
||||
TemporaryDir temp_dir;
|
||||
BuildInfo build_info(temp_dir.path);
|
||||
ASSERT_TRUE(build_info.ParseTargetFile(zip_file.path, false));
|
||||
|
||||
std::map<string, string> expected_result = {
|
||||
{ "ro.build.id", "OPR1.170510.001" },
|
||||
{ "ro.build.display.id", "OPR1.170510.001 dev-keys" },
|
||||
{ "ro.build.version.incremental", "3993052" },
|
||||
{ "ro.build.version.release", "O" },
|
||||
{ "ro.build.date", "Wed May 10 11:10:29 UTC 2017" },
|
||||
{ "ro.build.date.utc", "1494414629" },
|
||||
{ "ro.build.type", "user" },
|
||||
{ "ro.build.tags", "dev-keys" },
|
||||
{ "ro.build.flavor", "angler-user" },
|
||||
{ "ro.product.brand", "google" },
|
||||
{ "ro.product.name", "angler" },
|
||||
{ "ro.product.device", "angler" },
|
||||
};
|
||||
|
||||
for (const auto& [key, value] : expected_result) {
|
||||
ASSERT_EQ(value, build_info.GetProperty(key, ""));
|
||||
}
|
||||
|
||||
// Check that the temp files for each block device are created successfully.
|
||||
for (auto name : { "/dev/block/by-name/system", "/dev/block/by-name/recovery",
|
||||
"/dev/block/by-name/boot", "/dev/block/by-name/misc" }) {
|
||||
ASSERT_EQ(0, access(build_info.FindBlockDeviceName(name).c_str(), R_OK));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UpdateSimulatorTest, RunUpdateSmoke) {
|
||||
string recovery_img_string = "recovery.img";
|
||||
string boot_img_string = "boot.img";
|
||||
|
||||
std::map<string, string> src_entries{
|
||||
{ "META/misc_info.txt", "extfs_sparse_flag=-s" },
|
||||
{ "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
|
||||
{ "SYSTEM/build.prop", build_prop_string_ },
|
||||
{ "IMAGES/recovery.img", "" },
|
||||
{ "IMAGES/boot.img", boot_img_string },
|
||||
{ "IMAGES/system.img", sparse_system_string_ },
|
||||
};
|
||||
|
||||
// Construct the source target-files.
|
||||
TemporaryFile src_tf;
|
||||
AddZipEntries(src_tf.release(), src_entries);
|
||||
|
||||
string recovery_from_boot;
|
||||
CreateBsdiffPatch(boot_img_string, recovery_img_string, &recovery_from_boot);
|
||||
|
||||
// Set up the apply patch commands to patch the recovery image.
|
||||
string recovery_sha1 = CalculateSha1(recovery_img_string);
|
||||
string boot_sha1 = CalculateSha1(boot_img_string);
|
||||
string apply_patch_source_string = android::base::StringPrintf(
|
||||
"EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str());
|
||||
string apply_patch_target_string = android::base::StringPrintf(
|
||||
"EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str());
|
||||
string check_command = android::base::StringPrintf(
|
||||
R"(patch_partition_check("%s", "%s") || abort("check failed");)",
|
||||
apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
|
||||
string patch_command = android::base::StringPrintf(
|
||||
R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("patch failed");)",
|
||||
apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
|
||||
|
||||
// Add the commands to update the system image. Test common commands:
|
||||
// * getprop
|
||||
// * ui_print
|
||||
// * patch_partition
|
||||
// * package_extract_file (single argument)
|
||||
// * block_image_verify, block_image_update
|
||||
string tgt_system_string = string(4096, 'a');
|
||||
string system_patch;
|
||||
CreateBsdiffPatch(raw_system_string_, tgt_system_string, &system_patch);
|
||||
|
||||
string tgt_system_hash = CalculateSha1(tgt_system_string);
|
||||
string src_system_hash = CalculateSha1(raw_system_string_);
|
||||
|
||||
std::vector<std::string> transfer_list = {
|
||||
"4",
|
||||
"1",
|
||||
"0",
|
||||
"0",
|
||||
android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,1 1 2,0,1", system_patch.size(),
|
||||
src_system_hash.c_str(), tgt_system_hash.c_str()),
|
||||
};
|
||||
|
||||
// Construct the updater_script.
|
||||
std::vector<string> updater_commands = {
|
||||
R"(getprop("ro.product.device") == "angler" || abort("This package is for \"angler\"");)",
|
||||
R"(ui_print("Source: angler/OPR1.170510.001");)",
|
||||
check_command,
|
||||
patch_command,
|
||||
R"(block_image_verify("/dev/block/by-name/system", )"
|
||||
R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )"
|
||||
R"(abort("Failed to verify system.");)",
|
||||
R"(block_image_update("/dev/block/by-name/system", )"
|
||||
R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )"
|
||||
R"(abort("Failed to verify system.");)",
|
||||
};
|
||||
string updater_script = android::base::Join(updater_commands, '\n');
|
||||
|
||||
// Construct the ota update package.
|
||||
std::map<string, string> ota_entries{
|
||||
{ "system.new.dat", "" },
|
||||
{ "system.patch.dat", system_patch },
|
||||
{ "system.transfer.list", android::base::Join(transfer_list, '\n') },
|
||||
{ "META-INF/com/google/android/updater-script", updater_script },
|
||||
{ "patch.p", recovery_from_boot },
|
||||
};
|
||||
|
||||
TemporaryFile ota_package;
|
||||
AddZipEntries(ota_package.release(), ota_entries);
|
||||
|
||||
RunSimulation(src_tf.path, ota_package.path, true);
|
||||
}
|
||||
|
||||
TEST_F(UpdateSimulatorTest, RunUpdateUnrecognizedFunction) {
|
||||
std::map<string, string> src_entries{
|
||||
{ "META/misc_info.txt", "extfs_sparse_flag=-s" },
|
||||
{ "IMAGES/system.img", sparse_system_string_ },
|
||||
{ "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
|
||||
{ "SYSTEM/build.prop", build_prop_string_ },
|
||||
};
|
||||
|
||||
TemporaryFile src_tf;
|
||||
AddZipEntries(src_tf.release(), src_entries);
|
||||
|
||||
std::map<string, string> ota_entries{
|
||||
{ "system.new.dat", "" },
|
||||
{ "system.patch.dat", "" },
|
||||
{ "system.transfer.list", "" },
|
||||
{ "META-INF/com/google/android/updater-script", R"(bad_function("");)" },
|
||||
};
|
||||
|
||||
TemporaryFile ota_package;
|
||||
AddZipEntries(ota_package.release(), ota_entries);
|
||||
|
||||
RunSimulation(src_tf.path, ota_package.path, false);
|
||||
}
|
||||
|
||||
TEST_F(UpdateSimulatorTest, RunUpdateApplyPatchFailed) {
|
||||
string recovery_img_string = "recovery.img";
|
||||
string boot_img_string = "boot.img";
|
||||
|
||||
std::map<string, string> src_entries{
|
||||
{ "META/misc_info.txt", "extfs_sparse_flag=-s" },
|
||||
{ "IMAGES/recovery.img", "" },
|
||||
{ "IMAGES/boot.img", boot_img_string },
|
||||
{ "IMAGES/system.img", sparse_system_string_ },
|
||||
{ "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ },
|
||||
{ "SYSTEM/build.prop", build_prop_string_ },
|
||||
};
|
||||
|
||||
TemporaryFile src_tf;
|
||||
AddZipEntries(src_tf.release(), src_entries);
|
||||
|
||||
string recovery_sha1 = CalculateSha1(recovery_img_string);
|
||||
string boot_sha1 = CalculateSha1(boot_img_string);
|
||||
string apply_patch_source_string = android::base::StringPrintf(
|
||||
"EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str());
|
||||
string apply_patch_target_string = android::base::StringPrintf(
|
||||
"EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str());
|
||||
string check_command = android::base::StringPrintf(
|
||||
R"(patch_partition_check("%s", "%s") || abort("check failed");)",
|
||||
apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
|
||||
string patch_command = android::base::StringPrintf(
|
||||
R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("failed");)",
|
||||
apply_patch_target_string.c_str(), apply_patch_source_string.c_str());
|
||||
|
||||
// Give an invalid recovery patch and expect the apply patch to fail.
|
||||
// TODO(xunchang) check the cause code.
|
||||
std::vector<string> updater_commands = {
|
||||
R"(ui_print("Source: angler/OPR1.170510.001");)",
|
||||
check_command,
|
||||
patch_command,
|
||||
};
|
||||
|
||||
string updater_script = android::base::Join(updater_commands, '\n');
|
||||
std::map<string, string> ota_entries{
|
||||
{ "system.new.dat", "" },
|
||||
{ "system.patch.dat", "" },
|
||||
{ "system.transfer.list", "" },
|
||||
{ "META-INF/com/google/android/updater-script", updater_script },
|
||||
{ "patch.p", "random string" },
|
||||
};
|
||||
|
||||
TemporaryFile ota_package;
|
||||
AddZipEntries(ota_package.release(), ota_entries);
|
||||
|
||||
RunSimulation(src_tf.path, ota_package.path, false);
|
||||
}
|
|
@ -132,6 +132,11 @@ bool TargetFile::ReadEntryToString(const std::string_view name, std::string* con
|
|||
return false;
|
||||
}
|
||||
|
||||
if (entry.uncompressed_length == 0) {
|
||||
content->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
content->resize(entry.uncompressed_length);
|
||||
if (auto extract_err = ExtractToMemory(
|
||||
handle_, &entry, reinterpret_cast<uint8_t*>(&content->at(0)), entry.uncompressed_length);
|
||||
|
|
Loading…
Reference in a new issue