Revert "Remove non-AB unittests"
This reverts commit f203ee526f
.
Reason for revert: b/340571274
Merged-In: I3d82d9031446be355d8a1d077ab83283c7cc769c
Change-Id: I36d6246a32a83545b1d1b708372e8d3d72b4a041
This commit is contained in:
parent
f203ee526f
commit
63ea13852d
15 changed files with 4094 additions and 20 deletions
|
@ -35,10 +35,7 @@ package {
|
|||
// See: http://go/android-license-faq
|
||||
license {
|
||||
name: "bootable_recovery_license",
|
||||
visibility: [
|
||||
":__subpackages__",
|
||||
"//bootable/deprecated-ota:__subpackages__",
|
||||
],
|
||||
visibility: [":__subpackages__"],
|
||||
license_kinds: [
|
||||
"SPDX-license-identifier-Apache-2.0",
|
||||
"SPDX-license-identifier-MIT",
|
||||
|
@ -178,9 +175,11 @@ cc_binary {
|
|||
required: [
|
||||
"e2fsdroid.recovery",
|
||||
"init_recovery.rc",
|
||||
"librecovery_ui_ext",
|
||||
"minadbd",
|
||||
"mke2fs.conf.recovery",
|
||||
"mke2fs.recovery",
|
||||
"recovery_deps",
|
||||
"ueventd.rc.recovery",
|
||||
],
|
||||
}
|
||||
|
|
|
@ -84,3 +84,5 @@ endif
|
|||
|
||||
include $(BUILD_PHONY_PACKAGE)
|
||||
|
||||
include \
|
||||
$(LOCAL_PATH)/updater/Android.mk \
|
||||
|
|
|
@ -12,12 +12,6 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
soong_namespace {
|
||||
imports: [
|
||||
"bootable/recovery/edify",
|
||||
],
|
||||
}
|
||||
|
||||
package {
|
||||
default_applicable_licenses: ["bootable_recovery_applypatch_license"],
|
||||
}
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
soong_namespace {}
|
||||
|
||||
package {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
|
|
|
@ -98,6 +98,5 @@ cc_library_static {
|
|||
"//bootable/recovery/install",
|
||||
"//bootable/recovery/minadbd",
|
||||
"//bootable/recovery/tests",
|
||||
"//bootable/deprecated-ota:__subpackages__",
|
||||
],
|
||||
}
|
||||
|
|
|
@ -62,6 +62,9 @@ cc_defaults {
|
|||
|
||||
// libapplypatch, libapplypatch_modes
|
||||
libapplypatch_static_libs = [
|
||||
"libapplypatch_modes",
|
||||
"libapplypatch",
|
||||
"libedify",
|
||||
"libotautil",
|
||||
"libbsdiff",
|
||||
"libbspatch",
|
||||
|
@ -123,10 +126,16 @@ cc_test {
|
|||
|
||||
defaults: [
|
||||
"recovery_test_defaults",
|
||||
"libupdater_defaults",
|
||||
"libupdater_device_defaults",
|
||||
],
|
||||
|
||||
test_suites: ["device-tests"],
|
||||
|
||||
tidy_timeout_srcs: [
|
||||
"unit/commands_test.cpp",
|
||||
],
|
||||
|
||||
srcs: [
|
||||
"unit/*.cpp",
|
||||
],
|
||||
|
@ -144,6 +153,8 @@ cc_test {
|
|||
"libminui",
|
||||
"librecovery_utils",
|
||||
"libotautil",
|
||||
"libupdater_device",
|
||||
"libupdater_core",
|
||||
"libupdate_verifier",
|
||||
|
||||
"libprotobuf-cpp-lite",
|
||||
|
@ -180,6 +191,11 @@ cc_test_host {
|
|||
|
||||
defaults: [
|
||||
"recovery_test_defaults",
|
||||
"libupdater_defaults",
|
||||
],
|
||||
|
||||
tidy_timeout_srcs: [
|
||||
"unit/host/imgdiff_test.cpp",
|
||||
],
|
||||
|
||||
srcs: [
|
||||
|
@ -187,6 +203,9 @@ cc_test_host {
|
|||
],
|
||||
|
||||
static_libs: [
|
||||
"libupdater_host",
|
||||
"libupdater_core",
|
||||
"libimgdiff",
|
||||
"libbsdiff",
|
||||
"libdivsufsort64",
|
||||
"libdivsufsort",
|
||||
|
|
198
tests/unit/applypatch_modes_test.cpp
Normal file
198
tests/unit/applypatch_modes_test.cpp
Normal file
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* Copyright (C) 2016 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 agree 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <bsdiff/bsdiff.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include "applypatch/applypatch_modes.h"
|
||||
#include "common/test_constants.h"
|
||||
#include "otautil/paths.h"
|
||||
#include "otautil/print_sha1.h"
|
||||
#include "otautil/sysutil.h"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
// Loads a given partition and returns a string of form "EMMC:name:size:hash".
|
||||
static std::string GetEmmcTargetString(const std::string& filename,
|
||||
const std::string& display_name = "") {
|
||||
std::string data;
|
||||
if (!android::base::ReadFileToString(filename, &data)) {
|
||||
PLOG(ERROR) << "Failed to read " << filename;
|
||||
return {};
|
||||
}
|
||||
|
||||
uint8_t digest[SHA_DIGEST_LENGTH];
|
||||
SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest);
|
||||
|
||||
return "EMMC:"s + (display_name.empty() ? filename : display_name) + ":" +
|
||||
std::to_string(data.size()) + ":" + print_sha1(digest);
|
||||
}
|
||||
|
||||
class ApplyPatchModesTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
source = GetEmmcTargetString(from_testdata_base("boot.img"));
|
||||
ASSERT_FALSE(source.empty());
|
||||
|
||||
std::string recovery_file = from_testdata_base("recovery.img");
|
||||
recovery = GetEmmcTargetString(recovery_file);
|
||||
ASSERT_FALSE(recovery.empty());
|
||||
|
||||
ASSERT_TRUE(android::base::WriteStringToFile("", patched_file_.path));
|
||||
target = GetEmmcTargetString(recovery_file, patched_file_.path);
|
||||
ASSERT_FALSE(target.empty());
|
||||
|
||||
Paths::Get().set_cache_temp_source(cache_source_.path);
|
||||
}
|
||||
|
||||
std::string source;
|
||||
std::string target;
|
||||
std::string recovery;
|
||||
|
||||
private:
|
||||
TemporaryFile cache_source_;
|
||||
TemporaryFile patched_file_;
|
||||
};
|
||||
|
||||
static int InvokeApplyPatchModes(const std::vector<std::string>& args) {
|
||||
auto args_to_call = StringVectorToNullTerminatedArray(args);
|
||||
return applypatch_modes(args_to_call.size() - 1, args_to_call.data());
|
||||
}
|
||||
|
||||
static void VerifyPatchedTarget(const std::string& target) {
|
||||
std::vector<std::string> pieces = android::base::Split(target, ":");
|
||||
ASSERT_EQ(4, pieces.size());
|
||||
ASSERT_EQ("EMMC", pieces[0]);
|
||||
|
||||
std::string patched_emmc = GetEmmcTargetString(pieces[1]);
|
||||
ASSERT_FALSE(patched_emmc.empty());
|
||||
ASSERT_EQ(target, patched_emmc);
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchModesTest, InvalidArgs) {
|
||||
// At least two args (including the filename).
|
||||
ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch" }));
|
||||
|
||||
// Unrecognized args.
|
||||
ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch", "-x" }));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchModesTest, PatchModeEmmcTarget) {
|
||||
std::vector<std::string> args{
|
||||
"applypatch",
|
||||
"--bonus",
|
||||
from_testdata_base("bonus.file"),
|
||||
"--patch",
|
||||
from_testdata_base("recovery-from-boot.p"),
|
||||
"--target",
|
||||
target,
|
||||
"--source",
|
||||
source,
|
||||
};
|
||||
ASSERT_EQ(0, InvokeApplyPatchModes(args));
|
||||
VerifyPatchedTarget(target);
|
||||
}
|
||||
|
||||
// Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has
|
||||
// everything).
|
||||
TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithoutBonusFile) {
|
||||
std::vector<std::string> args{
|
||||
"applypatch", "--patch", from_testdata_base("recovery-from-boot-with-bonus.p"),
|
||||
"--target", target, "--source",
|
||||
source,
|
||||
};
|
||||
|
||||
ASSERT_EQ(0, InvokeApplyPatchModes(args));
|
||||
VerifyPatchedTarget(target);
|
||||
}
|
||||
|
||||
// Ensures that applypatch works with a bsdiff based recovery-from-boot.p.
|
||||
TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithBsdiffPatch) {
|
||||
// Generate the bsdiff patch of recovery-from-boot.p.
|
||||
std::string src_content;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("boot.img"), &src_content));
|
||||
|
||||
std::string tgt_content;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("recovery.img"), &tgt_content));
|
||||
|
||||
TemporaryFile patch_file;
|
||||
ASSERT_EQ(0,
|
||||
bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()), src_content.size(),
|
||||
reinterpret_cast<const uint8_t*>(tgt_content.data()), tgt_content.size(),
|
||||
patch_file.path, nullptr));
|
||||
|
||||
std::vector<std::string> args{
|
||||
"applypatch", "--patch", patch_file.path, "--target", target, "--source", source,
|
||||
};
|
||||
ASSERT_EQ(0, InvokeApplyPatchModes(args));
|
||||
VerifyPatchedTarget(target);
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchModesTest, PatchModeInvalidArgs) {
|
||||
// Invalid bonus file.
|
||||
std::vector<std::string> args{
|
||||
"applypatch", "--bonus", "/doesntexist", "--patch", from_testdata_base("recovery-from-boot.p"),
|
||||
"--target", target, "--source", source,
|
||||
};
|
||||
ASSERT_NE(0, InvokeApplyPatchModes(args));
|
||||
|
||||
// With bonus file, but missing args.
|
||||
ASSERT_NE(0,
|
||||
InvokeApplyPatchModes({ "applypatch", "--bonus", from_testdata_base("bonus.file") }));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchModesTest, FlashMode) {
|
||||
std::vector<std::string> args{
|
||||
"applypatch", "--flash", from_testdata_base("recovery.img"), "--target", target,
|
||||
};
|
||||
ASSERT_EQ(0, InvokeApplyPatchModes(args));
|
||||
VerifyPatchedTarget(target);
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchModesTest, FlashModeInvalidArgs) {
|
||||
std::vector<std::string> args{
|
||||
"applypatch", "--bonus", from_testdata_base("bonus.file"), "--flash", source,
|
||||
"--target", target,
|
||||
};
|
||||
ASSERT_NE(0, InvokeApplyPatchModes(args));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchModesTest, CheckMode) {
|
||||
ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--check", recovery }));
|
||||
ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--check", source }));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchModesTest, CheckModeInvalidArgs) {
|
||||
ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch", "--check" }));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchModesTest, CheckModeNonEmmcTarget) {
|
||||
ASSERT_NE(0, InvokeApplyPatchModes({ "applypatch", "--check", from_testdata_base("boot.img") }));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchModesTest, ShowLicenses) {
|
||||
ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--license" }));
|
||||
}
|
290
tests/unit/applypatch_test.cpp
Normal file
290
tests/unit/applypatch_test.cpp
Normal file
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
* Copyright (C) 2016 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 agree 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 <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "applypatch/applypatch.h"
|
||||
#include "common/test_constants.h"
|
||||
#include "edify/expr.h"
|
||||
#include "otautil/paths.h"
|
||||
#include "otautil/print_sha1.h"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
class ApplyPatchTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
source_file = from_testdata_base("boot.img");
|
||||
FileContents boot_fc;
|
||||
ASSERT_TRUE(LoadFileContents(source_file, &boot_fc));
|
||||
source_size = boot_fc.data.size();
|
||||
source_sha1 = print_sha1(boot_fc.sha1);
|
||||
|
||||
target_file = from_testdata_base("recovery.img");
|
||||
FileContents recovery_fc;
|
||||
ASSERT_TRUE(LoadFileContents(target_file, &recovery_fc));
|
||||
target_size = recovery_fc.data.size();
|
||||
target_sha1 = print_sha1(recovery_fc.sha1);
|
||||
|
||||
source_partition = Partition(source_file, source_size, source_sha1);
|
||||
target_partition = Partition(partition_file.path, target_size, target_sha1);
|
||||
|
||||
srand(time(nullptr));
|
||||
bad_sha1_a = android::base::StringPrintf("%040x", rand());
|
||||
bad_sha1_b = android::base::StringPrintf("%040x", rand());
|
||||
|
||||
// Reset the cache backup file.
|
||||
Paths::Get().set_cache_temp_source(cache_temp_source.path);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
ASSERT_TRUE(android::base::RemoveFileIfExists(cache_temp_source.path));
|
||||
}
|
||||
|
||||
std::string source_file;
|
||||
std::string source_sha1;
|
||||
size_t source_size;
|
||||
|
||||
std::string target_file;
|
||||
std::string target_sha1;
|
||||
size_t target_size;
|
||||
|
||||
std::string bad_sha1_a;
|
||||
std::string bad_sha1_b;
|
||||
|
||||
Partition source_partition;
|
||||
Partition target_partition;
|
||||
|
||||
private:
|
||||
TemporaryFile partition_file;
|
||||
TemporaryFile cache_temp_source;
|
||||
};
|
||||
|
||||
TEST_F(ApplyPatchTest, CheckPartition) {
|
||||
ASSERT_TRUE(CheckPartition(source_partition));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchTest, CheckPartition_Mismatching) {
|
||||
ASSERT_FALSE(CheckPartition(Partition(source_file, target_size, target_sha1)));
|
||||
ASSERT_FALSE(CheckPartition(Partition(source_file, source_size, bad_sha1_a)));
|
||||
|
||||
ASSERT_FALSE(CheckPartition(Partition(source_file, source_size - 1, source_sha1)));
|
||||
ASSERT_FALSE(CheckPartition(Partition(source_file, source_size + 1, source_sha1)));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchTest, PatchPartitionCheck) {
|
||||
ASSERT_TRUE(PatchPartitionCheck(target_partition, source_partition));
|
||||
|
||||
ASSERT_TRUE(
|
||||
PatchPartitionCheck(Partition(source_file, source_size - 1, source_sha1), source_partition));
|
||||
|
||||
ASSERT_TRUE(
|
||||
PatchPartitionCheck(Partition(source_file, source_size + 1, source_sha1), source_partition));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup) {
|
||||
ASSERT_FALSE(
|
||||
PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
|
||||
|
||||
Paths::Get().set_cache_temp_source(source_file);
|
||||
ASSERT_TRUE(
|
||||
PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup_BothCorrupted) {
|
||||
ASSERT_FALSE(
|
||||
PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
|
||||
|
||||
Paths::Get().set_cache_temp_source(target_file);
|
||||
ASSERT_FALSE(
|
||||
PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
|
||||
}
|
||||
|
||||
TEST_F(ApplyPatchTest, PatchPartition) {
|
||||
FileContents patch_fc;
|
||||
ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot.p"), &patch_fc));
|
||||
Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend()));
|
||||
|
||||
FileContents bonus_fc;
|
||||
ASSERT_TRUE(LoadFileContents(from_testdata_base("bonus.file"), &bonus_fc));
|
||||
Value bonus(Value::Type::BLOB, std::string(bonus_fc.data.cbegin(), bonus_fc.data.cend()));
|
||||
|
||||
ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, &bonus, false));
|
||||
}
|
||||
|
||||
// Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has
|
||||
// everything).
|
||||
TEST_F(ApplyPatchTest, PatchPartitionWithoutBonusFile) {
|
||||
FileContents patch_fc;
|
||||
ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot-with-bonus.p"), &patch_fc));
|
||||
Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend()));
|
||||
|
||||
ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, nullptr, false));
|
||||
}
|
||||
|
||||
class FreeCacheTest : public ::testing::Test {
|
||||
protected:
|
||||
static constexpr size_t PARTITION_SIZE = 4096 * 10;
|
||||
|
||||
// Returns a sorted list of files in |dirname|.
|
||||
static std::vector<std::string> FindFilesInDir(const std::string& dirname) {
|
||||
std::vector<std::string> file_list;
|
||||
|
||||
std::unique_ptr<DIR, decltype(&closedir)> d(opendir(dirname.c_str()), closedir);
|
||||
struct dirent* de;
|
||||
while ((de = readdir(d.get())) != 0) {
|
||||
std::string path = dirname + "/" + de->d_name;
|
||||
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
|
||||
file_list.emplace_back(de->d_name);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(file_list.begin(), file_list.end());
|
||||
return file_list;
|
||||
}
|
||||
|
||||
void AddFilesToDir(const std::string& dir, const std::vector<std::string>& files) {
|
||||
std::string zeros(4096, 0);
|
||||
for (const auto& file : files) {
|
||||
temporary_files_.push_back(dir + "/" + file);
|
||||
ASSERT_TRUE(android::base::WriteStringToFile(zeros, temporary_files_.back()));
|
||||
}
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
Paths::Get().set_cache_log_directory(mock_log_dir.path);
|
||||
temporary_files_.clear();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
for (const auto& file : temporary_files_) {
|
||||
ASSERT_TRUE(android::base::RemoveFileIfExists(file));
|
||||
}
|
||||
}
|
||||
|
||||
// A mock method to calculate the free space. It assumes the partition has a total size of 40960
|
||||
// bytes and all files are 4096 bytes in size.
|
||||
static size_t MockFreeSpaceChecker(const std::string& dirname) {
|
||||
std::vector<std::string> files = FindFilesInDir(dirname);
|
||||
return PARTITION_SIZE - 4096 * files.size();
|
||||
}
|
||||
|
||||
TemporaryDir mock_cache;
|
||||
TemporaryDir mock_log_dir;
|
||||
|
||||
private:
|
||||
std::vector<std::string> temporary_files_;
|
||||
};
|
||||
|
||||
TEST_F(FreeCacheTest, FreeCacheSmoke) {
|
||||
std::vector<std::string> files = { "file1", "file2", "file3" };
|
||||
AddFilesToDir(mock_cache.path, files);
|
||||
ASSERT_EQ(files, FindFilesInDir(mock_cache.path));
|
||||
ASSERT_EQ(4096 * 7, MockFreeSpaceChecker(mock_cache.path));
|
||||
|
||||
ASSERT_TRUE(RemoveFilesInDirectory(4096 * 9, mock_cache.path, MockFreeSpaceChecker));
|
||||
|
||||
ASSERT_EQ(std::vector<std::string>{ "file3" }, FindFilesInDir(mock_cache.path));
|
||||
ASSERT_EQ(4096 * 9, MockFreeSpaceChecker(mock_cache.path));
|
||||
}
|
||||
|
||||
TEST_F(FreeCacheTest, FreeCacheFreeSpaceCheckerError) {
|
||||
std::vector<std::string> files{ "file1", "file2", "file3" };
|
||||
AddFilesToDir(mock_cache.path, files);
|
||||
ASSERT_EQ(files, FindFilesInDir(mock_cache.path));
|
||||
ASSERT_EQ(4096 * 7, MockFreeSpaceChecker(mock_cache.path));
|
||||
|
||||
ASSERT_FALSE(
|
||||
RemoveFilesInDirectory(4096 * 9, mock_cache.path, [](const std::string&) { return -1; }));
|
||||
}
|
||||
|
||||
TEST_F(FreeCacheTest, FreeCacheOpenFile) {
|
||||
std::vector<std::string> files = { "file1", "file2" };
|
||||
AddFilesToDir(mock_cache.path, files);
|
||||
ASSERT_EQ(files, FindFilesInDir(mock_cache.path));
|
||||
ASSERT_EQ(4096 * 8, MockFreeSpaceChecker(mock_cache.path));
|
||||
|
||||
std::string file1_path = mock_cache.path + "/file1"s;
|
||||
android::base::unique_fd fd(open(file1_path.c_str(), O_RDONLY));
|
||||
|
||||
// file1 can't be deleted as it's opened by us.
|
||||
ASSERT_FALSE(RemoveFilesInDirectory(4096 * 10, mock_cache.path, MockFreeSpaceChecker));
|
||||
|
||||
ASSERT_EQ(std::vector<std::string>{ "file1" }, FindFilesInDir(mock_cache.path));
|
||||
}
|
||||
|
||||
TEST_F(FreeCacheTest, FreeCacheLogsSmoke) {
|
||||
std::vector<std::string> log_files = { "last_log", "last_log.1", "last_kmsg.2", "last_log.5",
|
||||
"last_log.10" };
|
||||
AddFilesToDir(mock_log_dir.path, log_files);
|
||||
ASSERT_EQ(4096 * 5, MockFreeSpaceChecker(mock_log_dir.path));
|
||||
|
||||
ASSERT_TRUE(RemoveFilesInDirectory(4096 * 8, mock_log_dir.path, MockFreeSpaceChecker));
|
||||
|
||||
// Logs with a higher index will be deleted first
|
||||
std::vector<std::string> expected = { "last_log", "last_log.1" };
|
||||
ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path));
|
||||
ASSERT_EQ(4096 * 8, MockFreeSpaceChecker(mock_log_dir.path));
|
||||
}
|
||||
|
||||
TEST_F(FreeCacheTest, FreeCacheLogsStringComparison) {
|
||||
std::vector<std::string> log_files = { "last_log.1", "last_kmsg.1", "last_log.not_number",
|
||||
"last_kmsgrandom" };
|
||||
AddFilesToDir(mock_log_dir.path, log_files);
|
||||
ASSERT_EQ(4096 * 6, MockFreeSpaceChecker(mock_log_dir.path));
|
||||
|
||||
ASSERT_TRUE(RemoveFilesInDirectory(4096 * 9, mock_log_dir.path, MockFreeSpaceChecker));
|
||||
|
||||
// Logs with incorrect format will be deleted first; and the last_kmsg with the same index is
|
||||
// deleted before last_log.
|
||||
std::vector<std::string> expected = { "last_log.1" };
|
||||
ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path));
|
||||
ASSERT_EQ(4096 * 9, MockFreeSpaceChecker(mock_log_dir.path));
|
||||
}
|
||||
|
||||
TEST_F(FreeCacheTest, FreeCacheLogsOtherFiles) {
|
||||
std::vector<std::string> log_files = { "last_install", "command", "block.map", "last_log",
|
||||
"last_kmsg.1" };
|
||||
AddFilesToDir(mock_log_dir.path, log_files);
|
||||
ASSERT_EQ(4096 * 5, MockFreeSpaceChecker(mock_log_dir.path));
|
||||
|
||||
ASSERT_FALSE(RemoveFilesInDirectory(4096 * 8, mock_log_dir.path, MockFreeSpaceChecker));
|
||||
|
||||
// Non log files in /cache/recovery won't be deleted.
|
||||
std::vector<std::string> expected = { "block.map", "command", "last_install" };
|
||||
ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path));
|
||||
}
|
554
tests/unit/commands_test.cpp
Normal file
554
tests/unit/commands_test.cpp
Normal file
|
@ -0,0 +1,554 @@
|
|||
/*
|
||||
* Copyright (C) 2018 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 <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include <android-base/strings.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include "otautil/print_sha1.h"
|
||||
#include "otautil/rangeset.h"
|
||||
#include "private/commands.h"
|
||||
|
||||
TEST(CommandsTest, ParseType) {
|
||||
ASSERT_EQ(Command::Type::ZERO, Command::ParseType("zero"));
|
||||
ASSERT_EQ(Command::Type::NEW, Command::ParseType("new"));
|
||||
ASSERT_EQ(Command::Type::ERASE, Command::ParseType("erase"));
|
||||
ASSERT_EQ(Command::Type::MOVE, Command::ParseType("move"));
|
||||
ASSERT_EQ(Command::Type::BSDIFF, Command::ParseType("bsdiff"));
|
||||
ASSERT_EQ(Command::Type::IMGDIFF, Command::ParseType("imgdiff"));
|
||||
ASSERT_EQ(Command::Type::STASH, Command::ParseType("stash"));
|
||||
ASSERT_EQ(Command::Type::FREE, Command::ParseType("free"));
|
||||
ASSERT_EQ(Command::Type::COMPUTE_HASH_TREE, Command::ParseType("compute_hash_tree"));
|
||||
}
|
||||
|
||||
TEST(CommandsTest, ParseType_InvalidCommand) {
|
||||
ASSERT_EQ(Command::Type::LAST, Command::ParseType("foo"));
|
||||
ASSERT_EQ(Command::Type::LAST, Command::ParseType("bar"));
|
||||
}
|
||||
|
||||
TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksOnly) {
|
||||
const std::vector<std::string> tokens{
|
||||
"4,569884,569904,591946,592043",
|
||||
"117",
|
||||
"4,566779,566799,591946,592043",
|
||||
};
|
||||
TargetInfo target;
|
||||
SourceInfo source;
|
||||
std::string err;
|
||||
ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
|
||||
tokens, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
|
||||
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
|
||||
ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||
RangeSet({ { 569884, 569904 }, { 591946, 592043 } })),
|
||||
target);
|
||||
ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||
RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), {}, {}),
|
||||
source);
|
||||
ASSERT_EQ(117, source.blocks());
|
||||
}
|
||||
|
||||
TEST(CommandsTest, ParseTargetInfoAndSourceInfo_StashesOnly) {
|
||||
const std::vector<std::string> tokens{
|
||||
"2,350729,350731",
|
||||
"2",
|
||||
"-",
|
||||
"6ebcf8cf1f6be0bc49e7d4a864214251925d1d15:2,0,2",
|
||||
};
|
||||
TargetInfo target;
|
||||
SourceInfo source;
|
||||
std::string err;
|
||||
ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
|
||||
tokens, "6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", &target,
|
||||
"1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", &source, &err));
|
||||
ASSERT_EQ(
|
||||
TargetInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 350729, 350731 } })),
|
||||
target);
|
||||
ASSERT_EQ(
|
||||
SourceInfo("1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", {}, {},
|
||||
{
|
||||
StashInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 0, 2 } })),
|
||||
}),
|
||||
source);
|
||||
ASSERT_EQ(2, source.blocks());
|
||||
}
|
||||
|
||||
TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksAndStashes) {
|
||||
const std::vector<std::string> tokens{
|
||||
"4,611641,611643,636981,637075",
|
||||
"96",
|
||||
"4,636981,637075,770665,770666",
|
||||
"4,0,94,95,96",
|
||||
"9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95",
|
||||
};
|
||||
TargetInfo target;
|
||||
SourceInfo source;
|
||||
std::string err;
|
||||
ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
|
||||
tokens, "4734d1b241eb3d0f993714aaf7d665fae43772b6", &target,
|
||||
"a6cbdf3f416960f02189d3a814ec7e9e95c44a0d", &source, &err));
|
||||
ASSERT_EQ(TargetInfo("4734d1b241eb3d0f993714aaf7d665fae43772b6",
|
||||
RangeSet({ { 611641, 611643 }, { 636981, 637075 } })),
|
||||
target);
|
||||
ASSERT_EQ(SourceInfo(
|
||||
"a6cbdf3f416960f02189d3a814ec7e9e95c44a0d",
|
||||
RangeSet({ { 636981, 637075 }, { 770665, 770666 } }), // source ranges
|
||||
RangeSet({ { 0, 94 }, { 95, 96 } }), // source location
|
||||
{
|
||||
StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23", RangeSet({ { 94, 95 } })),
|
||||
}),
|
||||
source);
|
||||
ASSERT_EQ(96, source.blocks());
|
||||
}
|
||||
|
||||
TEST(CommandsTest, ParseTargetInfoAndSourceInfo_InvalidInput) {
|
||||
const std::vector<std::string> tokens{
|
||||
"4,611641,611643,636981,637075",
|
||||
"96",
|
||||
"4,636981,637075,770665,770666",
|
||||
"4,0,94,95,96",
|
||||
"9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95",
|
||||
};
|
||||
TargetInfo target;
|
||||
SourceInfo source;
|
||||
std::string err;
|
||||
|
||||
// Mismatching block count.
|
||||
{
|
||||
std::vector<std::string> tokens_copy(tokens);
|
||||
tokens_copy[1] = "97";
|
||||
ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
|
||||
tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
|
||||
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
|
||||
}
|
||||
|
||||
// Excess stashes (causing block count mismatch).
|
||||
{
|
||||
std::vector<std::string> tokens_copy(tokens);
|
||||
tokens_copy.push_back("e145a2f83a33334714ac65e34969c1f115e54a6f:2,0,22");
|
||||
ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
|
||||
tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
|
||||
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
|
||||
}
|
||||
|
||||
// Invalid args.
|
||||
for (size_t i = 0; i < tokens.size(); i++) {
|
||||
TargetInfo target;
|
||||
SourceInfo source;
|
||||
std::string err;
|
||||
ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
|
||||
std::vector<std::string>(tokens.cbegin() + i + 1, tokens.cend()),
|
||||
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
|
||||
"1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CommandsTest, Parse_EmptyInput) {
|
||||
std::string err;
|
||||
ASSERT_FALSE(Command::Parse("", 0, &err));
|
||||
ASSERT_EQ("invalid type", err);
|
||||
}
|
||||
|
||||
TEST(CommandsTest, Parse_ABORT_Allowed) {
|
||||
Command::abort_allowed_ = true;
|
||||
|
||||
const std::string input{ "abort" };
|
||||
std::string err;
|
||||
Command command = Command::Parse(input, 0, &err);
|
||||
ASSERT_TRUE(command);
|
||||
|
||||
ASSERT_EQ(TargetInfo(), command.target());
|
||||
ASSERT_EQ(SourceInfo(), command.source());
|
||||
ASSERT_EQ(StashInfo(), command.stash());
|
||||
ASSERT_EQ(PatchInfo(), command.patch());
|
||||
}
|
||||
|
||||
TEST(CommandsTest, Parse_ABORT_NotAllowed) {
|
||||
const std::string input{ "abort" };
|
||||
std::string err;
|
||||
Command command = Command::Parse(input, 0, &err);
|
||||
ASSERT_FALSE(command);
|
||||
}
|
||||
|
||||
TEST(CommandsTest, Parse_BSDIFF) {
|
||||
const std::string input{
|
||||
"bsdiff 0 148 "
|
||||
"f201a4e04bd3860da6ad47b957ef424d58a58f8c 9d5d223b4bc5c45dbd25a799c4f1a98466731599 "
|
||||
"4,565704,565752,566779,566799 "
|
||||
"68 4,64525,64545,565704,565752"
|
||||
};
|
||||
std::string err;
|
||||
Command command = Command::Parse(input, 1, &err);
|
||||
ASSERT_TRUE(command);
|
||||
|
||||
ASSERT_EQ(Command::Type::BSDIFF, command.type());
|
||||
ASSERT_EQ(1, command.index());
|
||||
ASSERT_EQ(input, command.cmdline());
|
||||
|
||||
ASSERT_EQ(TargetInfo("9d5d223b4bc5c45dbd25a799c4f1a98466731599",
|
||||
RangeSet({ { 565704, 565752 }, { 566779, 566799 } })),
|
||||
command.target());
|
||||
ASSERT_EQ(SourceInfo("f201a4e04bd3860da6ad47b957ef424d58a58f8c",
|
||||
RangeSet({ { 64525, 64545 }, { 565704, 565752 } }), RangeSet(), {}),
|
||||
command.source());
|
||||
ASSERT_EQ(StashInfo(), command.stash());
|
||||
ASSERT_EQ(PatchInfo(0, 148), command.patch());
|
||||
}
|
||||
|
||||
TEST(CommandsTest, Parse_ERASE) {
|
||||
const std::string input{ "erase 2,5,10" };
|
||||
std::string err;
|
||||
Command command = Command::Parse(input, 2, &err);
|
||||
ASSERT_TRUE(command);
|
||||
|
||||
ASSERT_EQ(Command::Type::ERASE, command.type());
|
||||
ASSERT_EQ(2, command.index());
|
||||
ASSERT_EQ(input, command.cmdline());
|
||||
|
||||
ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 5, 10 } })), command.target());
|
||||
ASSERT_EQ(SourceInfo(), command.source());
|
||||
ASSERT_EQ(StashInfo(), command.stash());
|
||||
ASSERT_EQ(PatchInfo(), command.patch());
|
||||
}
|
||||
|
||||
TEST(CommandsTest, Parse_FREE) {
|
||||
const std::string input{ "free hash1" };
|
||||
std::string err;
|
||||
Command command = Command::Parse(input, 3, &err);
|
||||
ASSERT_TRUE(command);
|
||||
|
||||
ASSERT_EQ(Command::Type::FREE, command.type());
|
||||
ASSERT_EQ(3, command.index());
|
||||
ASSERT_EQ(input, command.cmdline());
|
||||
|
||||
ASSERT_EQ(TargetInfo(), command.target());
|
||||
ASSERT_EQ(SourceInfo(), command.source());
|
||||
ASSERT_EQ(StashInfo("hash1", RangeSet()), command.stash());
|
||||
ASSERT_EQ(PatchInfo(), command.patch());
|
||||
}
|
||||
|
||||
TEST(CommandsTest, Parse_IMGDIFF) {
|
||||
const std::string input{
|
||||
"imgdiff 29629269 185 "
|
||||
"a6b1c49aed1b57a2aab1ec3e1505b945540cd8db 51978f65035f584a8ef7afa941dacb6d5e862164 "
|
||||
"2,90851,90852 "
|
||||
"1 2,90851,90852"
|
||||
};
|
||||
std::string err;
|
||||
Command command = Command::Parse(input, 4, &err);
|
||||
ASSERT_TRUE(command);
|
||||
|
||||
ASSERT_EQ(Command::Type::IMGDIFF, command.type());
|
||||
ASSERT_EQ(4, command.index());
|
||||
ASSERT_EQ(input, command.cmdline());
|
||||
|
||||
ASSERT_EQ(TargetInfo("51978f65035f584a8ef7afa941dacb6d5e862164", RangeSet({ { 90851, 90852 } })),
|
||||
command.target());
|
||||
ASSERT_EQ(SourceInfo("a6b1c49aed1b57a2aab1ec3e1505b945540cd8db", RangeSet({ { 90851, 90852 } }),
|
||||
RangeSet(), {}),
|
||||
command.source());
|
||||
ASSERT_EQ(StashInfo(), command.stash());
|
||||
ASSERT_EQ(PatchInfo(29629269, 185), command.patch());
|
||||
}
|
||||
|
||||
TEST(CommandsTest, Parse_MOVE) {
|
||||
const std::string input{
|
||||
"move 1d74d1a60332fd38cf9405f1bae67917888da6cb "
|
||||
"4,569884,569904,591946,592043 117 4,566779,566799,591946,592043"
|
||||
};
|
||||
std::string err;
|
||||
Command command = Command::Parse(input, 5, &err);
|
||||
ASSERT_TRUE(command);
|
||||
|
||||
ASSERT_EQ(Command::Type::MOVE, command.type());
|
||||
ASSERT_EQ(5, command.index());
|
||||
ASSERT_EQ(input, command.cmdline());
|
||||
|
||||
ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||
RangeSet({ { 569884, 569904 }, { 591946, 592043 } })),
|
||||
command.target());
|
||||
ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||
RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), RangeSet(), {}),
|
||||
command.source());
|
||||
ASSERT_EQ(StashInfo(), command.stash());
|
||||
ASSERT_EQ(PatchInfo(), command.patch());
|
||||
}
|
||||
|
||||
TEST(CommandsTest, Parse_NEW) {
|
||||
const std::string input{ "new 4,3,5,10,12" };
|
||||
std::string err;
|
||||
Command command = Command::Parse(input, 6, &err);
|
||||
ASSERT_TRUE(command);
|
||||
|
||||
ASSERT_EQ(Command::Type::NEW, command.type());
|
||||
ASSERT_EQ(6, command.index());
|
||||
ASSERT_EQ(input, command.cmdline());
|
||||
|
||||
ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 3, 5 }, { 10, 12 } })), command.target());
|
||||
ASSERT_EQ(SourceInfo(), command.source());
|
||||
ASSERT_EQ(StashInfo(), command.stash());
|
||||
ASSERT_EQ(PatchInfo(), command.patch());
|
||||
}
|
||||
|
||||
TEST(CommandsTest, Parse_STASH) {
|
||||
const std::string input{ "stash hash1 2,5,10" };
|
||||
std::string err;
|
||||
Command command = Command::Parse(input, 7, &err);
|
||||
ASSERT_TRUE(command);
|
||||
|
||||
ASSERT_EQ(Command::Type::STASH, command.type());
|
||||
ASSERT_EQ(7, command.index());
|
||||
ASSERT_EQ(input, command.cmdline());
|
||||
|
||||
ASSERT_EQ(TargetInfo(), command.target());
|
||||
ASSERT_EQ(SourceInfo(), command.source());
|
||||
ASSERT_EQ(StashInfo("hash1", RangeSet({ { 5, 10 } })), command.stash());
|
||||
ASSERT_EQ(PatchInfo(), command.patch());
|
||||
}
|
||||
|
||||
TEST(CommandsTest, Parse_ZERO) {
|
||||
const std::string input{ "zero 2,1,5" };
|
||||
std::string err;
|
||||
Command command = Command::Parse(input, 8, &err);
|
||||
ASSERT_TRUE(command);
|
||||
|
||||
ASSERT_EQ(Command::Type::ZERO, command.type());
|
||||
ASSERT_EQ(8, command.index());
|
||||
ASSERT_EQ(input, command.cmdline());
|
||||
|
||||
ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 1, 5 } })), command.target());
|
||||
ASSERT_EQ(SourceInfo(), command.source());
|
||||
ASSERT_EQ(StashInfo(), command.stash());
|
||||
ASSERT_EQ(PatchInfo(), command.patch());
|
||||
}
|
||||
|
||||
TEST(CommandsTest, Parse_COMPUTE_HASH_TREE) {
|
||||
const std::string input{ "compute_hash_tree 2,0,1 2,3,4 sha1 unknown-salt unknown-root-hash" };
|
||||
std::string err;
|
||||
Command command = Command::Parse(input, 9, &err);
|
||||
ASSERT_TRUE(command);
|
||||
|
||||
ASSERT_EQ(Command::Type::COMPUTE_HASH_TREE, command.type());
|
||||
ASSERT_EQ(9, command.index());
|
||||
ASSERT_EQ(input, command.cmdline());
|
||||
|
||||
HashTreeInfo expected_info(RangeSet({ { 0, 1 } }), RangeSet({ { 3, 4 } }), "sha1", "unknown-salt",
|
||||
"unknown-root-hash");
|
||||
ASSERT_EQ(expected_info, command.hash_tree_info());
|
||||
ASSERT_EQ(TargetInfo(), command.target());
|
||||
ASSERT_EQ(SourceInfo(), command.source());
|
||||
ASSERT_EQ(StashInfo(), command.stash());
|
||||
ASSERT_EQ(PatchInfo(), command.patch());
|
||||
}
|
||||
|
||||
TEST(CommandsTest, Parse_InvalidNumberOfArgs) {
|
||||
Command::abort_allowed_ = true;
|
||||
|
||||
// Note that the case of having excess args in BSDIFF, IMGDIFF and MOVE is covered by
|
||||
// ParseTargetInfoAndSourceInfo_InvalidInput.
|
||||
std::vector<std::string> inputs{
|
||||
"abort foo",
|
||||
"bsdiff",
|
||||
"compute_hash_tree, 2,0,1 2,0,1 unknown-algorithm unknown-salt",
|
||||
"erase",
|
||||
"erase 4,3,5,10,12 hash1",
|
||||
"free",
|
||||
"free id1 id2",
|
||||
"imgdiff",
|
||||
"move",
|
||||
"new",
|
||||
"new 4,3,5,10,12 hash1",
|
||||
"stash",
|
||||
"stash id1",
|
||||
"stash id1 4,3,5,10,12 id2",
|
||||
"zero",
|
||||
"zero 4,3,5,10,12 hash2",
|
||||
};
|
||||
for (const auto& input : inputs) {
|
||||
std::string err;
|
||||
ASSERT_FALSE(Command::Parse(input, 0, &err));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SourceInfoTest, Overlaps) {
|
||||
ASSERT_TRUE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||
RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
|
||||
.Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||
RangeSet({ { 7, 9 }, { 16, 20 } }))));
|
||||
|
||||
ASSERT_TRUE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||
RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
|
||||
.Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||
RangeSet({ { 4, 7 }, { 16, 23 } }))));
|
||||
|
||||
ASSERT_FALSE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||
RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
|
||||
.Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||
RangeSet({ { 9, 16 } }))));
|
||||
}
|
||||
|
||||
TEST(SourceInfoTest, Overlaps_EmptySourceOrTarget) {
|
||||
ASSERT_FALSE(SourceInfo().Overlaps(TargetInfo()));
|
||||
|
||||
ASSERT_FALSE(SourceInfo().Overlaps(
|
||||
TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", RangeSet({ { 7, 9 }, { 16, 20 } }))));
|
||||
|
||||
ASSERT_FALSE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||
RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
|
||||
.Overlaps(TargetInfo()));
|
||||
}
|
||||
|
||||
TEST(SourceInfoTest, Overlaps_WithStashes) {
|
||||
ASSERT_FALSE(SourceInfo("a6cbdf3f416960f02189d3a814ec7e9e95c44a0d",
|
||||
RangeSet({ { 81, 175 }, { 265, 266 } }), // source ranges
|
||||
RangeSet({ { 0, 94 }, { 95, 96 } }), // source location
|
||||
{ StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23",
|
||||
RangeSet({ { 94, 95 } })) })
|
||||
.Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||
RangeSet({ { 175, 265 } }))));
|
||||
|
||||
ASSERT_TRUE(SourceInfo("a6cbdf3f416960f02189d3a814ec7e9e95c44a0d",
|
||||
RangeSet({ { 81, 175 }, { 265, 266 } }), // source ranges
|
||||
RangeSet({ { 0, 94 }, { 95, 96 } }), // source location
|
||||
{ StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23",
|
||||
RangeSet({ { 94, 95 } })) })
|
||||
.Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
|
||||
RangeSet({ { 265, 266 } }))));
|
||||
}
|
||||
|
||||
// The block size should be specified by the caller of ReadAll (i.e. from Command instance during
|
||||
// normal run).
|
||||
constexpr size_t kBlockSize = 4096;
|
||||
|
||||
TEST(SourceInfoTest, ReadAll) {
|
||||
// "2727756cfee3fbfe24bf5650123fd7743d7b3465" is the SHA-1 hex digest of 8192 * 'a'.
|
||||
const SourceInfo source("2727756cfee3fbfe24bf5650123fd7743d7b3465", RangeSet({ { 0, 2 } }), {},
|
||||
{});
|
||||
auto block_reader = [](const RangeSet& src, std::vector<uint8_t>* block_buffer) -> int {
|
||||
std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a');
|
||||
return 0;
|
||||
};
|
||||
auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
|
||||
std::vector<uint8_t> buffer(source.blocks() * kBlockSize);
|
||||
ASSERT_TRUE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader));
|
||||
ASSERT_EQ(source.blocks() * kBlockSize, buffer.size());
|
||||
|
||||
uint8_t digest[SHA_DIGEST_LENGTH];
|
||||
SHA1(buffer.data(), buffer.size(), digest);
|
||||
ASSERT_EQ(source.hash(), print_sha1(digest));
|
||||
}
|
||||
|
||||
TEST(SourceInfoTest, ReadAll_WithStashes) {
|
||||
const SourceInfo source(
|
||||
// SHA-1 hex digest of 8192 * 'a' + 4096 * 'b'.
|
||||
"ee3ebea26130769c10ad13604712100346d48660", RangeSet({ { 0, 2 } }), RangeSet({ { 0, 2 } }),
|
||||
{ StashInfo("1e41f7a59e80c6eb4dc043caae80d273f130bed8", RangeSet({ { 2, 3 } })) });
|
||||
auto block_reader = [](const RangeSet& src, std::vector<uint8_t>* block_buffer) -> int {
|
||||
std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a');
|
||||
return 0;
|
||||
};
|
||||
auto stash_reader = [](const std::string&, std::vector<uint8_t>* stash_buffer) -> int {
|
||||
std::fill_n(stash_buffer->begin(), kBlockSize, 'b');
|
||||
return 0;
|
||||
};
|
||||
std::vector<uint8_t> buffer(source.blocks() * kBlockSize);
|
||||
ASSERT_TRUE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader));
|
||||
ASSERT_EQ(source.blocks() * kBlockSize, buffer.size());
|
||||
|
||||
uint8_t digest[SHA_DIGEST_LENGTH];
|
||||
SHA1(buffer.data(), buffer.size(), digest);
|
||||
ASSERT_EQ(source.hash(), print_sha1(digest));
|
||||
}
|
||||
|
||||
TEST(SourceInfoTest, ReadAll_BufferTooSmall) {
|
||||
const SourceInfo source("2727756cfee3fbfe24bf5650123fd7743d7b3465", RangeSet({ { 0, 2 } }), {},
|
||||
{});
|
||||
auto block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return 0; };
|
||||
auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
|
||||
std::vector<uint8_t> buffer(source.blocks() * kBlockSize - 1);
|
||||
ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader));
|
||||
}
|
||||
|
||||
TEST(SourceInfoTest, ReadAll_FailingReader) {
|
||||
const SourceInfo source(
|
||||
"ee3ebea26130769c10ad13604712100346d48660", RangeSet({ { 0, 2 } }), RangeSet({ { 0, 2 } }),
|
||||
{ StashInfo("1e41f7a59e80c6eb4dc043caae80d273f130bed8", RangeSet({ { 2, 3 } })) });
|
||||
std::vector<uint8_t> buffer(source.blocks() * kBlockSize);
|
||||
auto failing_block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return -1; };
|
||||
auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
|
||||
ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, failing_block_reader, stash_reader));
|
||||
|
||||
auto block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return 0; };
|
||||
auto failing_stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return -1; };
|
||||
ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, block_reader, failing_stash_reader));
|
||||
}
|
||||
|
||||
TEST(TransferListTest, Parse) {
|
||||
std::vector<std::string> input_lines{
|
||||
"4", // version
|
||||
"2", // total blocks
|
||||
"1", // max stashed entries
|
||||
"1", // max stashed blocks
|
||||
"stash 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1",
|
||||
"move 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1 1 2,0,1",
|
||||
};
|
||||
|
||||
std::string err;
|
||||
TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err);
|
||||
ASSERT_TRUE(static_cast<bool>(transfer_list));
|
||||
ASSERT_EQ(4, transfer_list.version());
|
||||
ASSERT_EQ(2, transfer_list.total_blocks());
|
||||
ASSERT_EQ(1, transfer_list.stash_max_entries());
|
||||
ASSERT_EQ(1, transfer_list.stash_max_blocks());
|
||||
ASSERT_EQ(2U, transfer_list.commands().size());
|
||||
ASSERT_EQ(Command::Type::STASH, transfer_list.commands()[0].type());
|
||||
ASSERT_EQ(Command::Type::MOVE, transfer_list.commands()[1].type());
|
||||
}
|
||||
|
||||
TEST(TransferListTest, Parse_InvalidCommand) {
|
||||
std::vector<std::string> input_lines{
|
||||
"4", // version
|
||||
"2", // total blocks
|
||||
"1", // max stashed entries
|
||||
"1", // max stashed blocks
|
||||
"stash 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1",
|
||||
"move 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1 1",
|
||||
};
|
||||
|
||||
std::string err;
|
||||
TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err);
|
||||
ASSERT_FALSE(static_cast<bool>(transfer_list));
|
||||
}
|
||||
|
||||
TEST(TransferListTest, Parse_ZeroTotalBlocks) {
|
||||
std::vector<std::string> input_lines{
|
||||
"4", // version
|
||||
"0", // total blocks
|
||||
"0", // max stashed entries
|
||||
"0", // max stashed blocks
|
||||
};
|
||||
|
||||
std::string err;
|
||||
TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err);
|
||||
ASSERT_TRUE(static_cast<bool>(transfer_list));
|
||||
ASSERT_EQ(4, transfer_list.version());
|
||||
ASSERT_EQ(0, transfer_list.total_blocks());
|
||||
ASSERT_EQ(0, transfer_list.stash_max_entries());
|
||||
ASSERT_EQ(0, transfer_list.stash_max_blocks());
|
||||
ASSERT_TRUE(transfer_list.commands().empty());
|
||||
}
|
167
tests/unit/edify_test.cpp
Normal file
167
tests/unit/edify_test.cpp
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright (C) 2009 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 <memory>
|
||||
#include <string>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "edify/expr.h"
|
||||
|
||||
static void expect(const std::string& expr_str, const char* expected) {
|
||||
std::unique_ptr<Expr> e;
|
||||
int error_count = 0;
|
||||
EXPECT_EQ(0, ParseString(expr_str, &e, &error_count));
|
||||
EXPECT_EQ(0, error_count);
|
||||
|
||||
State state(expr_str, nullptr);
|
||||
|
||||
std::string result;
|
||||
bool status = Evaluate(&state, e, &result);
|
||||
|
||||
if (expected == nullptr) {
|
||||
EXPECT_FALSE(status);
|
||||
} else {
|
||||
EXPECT_STREQ(expected, result.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
class EdifyTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() {
|
||||
RegisterBuiltins();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(EdifyTest, parsing) {
|
||||
expect("a", "a");
|
||||
expect("\"a\"", "a");
|
||||
expect("\"\\x61\"", "a");
|
||||
expect("# this is a comment\n"
|
||||
" a\n"
|
||||
" \n",
|
||||
"a");
|
||||
}
|
||||
|
||||
TEST_F(EdifyTest, sequence) {
|
||||
// sequence operator
|
||||
expect("a; b; c", "c");
|
||||
}
|
||||
|
||||
TEST_F(EdifyTest, concat) {
|
||||
// string concat operator
|
||||
expect("a + b", "ab");
|
||||
expect("a + \n \"b\"", "ab");
|
||||
expect("a + b +\nc\n", "abc");
|
||||
|
||||
// string concat function
|
||||
expect("concat(a, b)", "ab");
|
||||
expect("concat(a,\n \"b\")", "ab");
|
||||
expect("concat(a + b,\nc,\"d\")", "abcd");
|
||||
expect("\"concat\"(a + b,\nc,\"d\")", "abcd");
|
||||
}
|
||||
|
||||
TEST_F(EdifyTest, logical) {
|
||||
// logical and
|
||||
expect("a && b", "b");
|
||||
expect("a && \"\"", "");
|
||||
expect("\"\" && b", "");
|
||||
expect("\"\" && \"\"", "");
|
||||
expect("\"\" && abort()", ""); // test short-circuiting
|
||||
expect("t && abort()", nullptr);
|
||||
|
||||
// logical or
|
||||
expect("a || b", "a");
|
||||
expect("a || \"\"", "a");
|
||||
expect("\"\" || b", "b");
|
||||
expect("\"\" || \"\"", "");
|
||||
expect("a || abort()", "a"); // test short-circuiting
|
||||
expect("\"\" || abort()", NULL);
|
||||
|
||||
// logical not
|
||||
expect("!a", "");
|
||||
expect("! \"\"", "t");
|
||||
expect("!!a", "t");
|
||||
}
|
||||
|
||||
TEST_F(EdifyTest, precedence) {
|
||||
// precedence
|
||||
expect("\"\" == \"\" && b", "b");
|
||||
expect("a + b == ab", "t");
|
||||
expect("ab == a + b", "t");
|
||||
expect("a + (b == ab)", "a");
|
||||
expect("(ab == a) + b", "b");
|
||||
}
|
||||
|
||||
TEST_F(EdifyTest, substring) {
|
||||
// substring function
|
||||
expect("is_substring(cad, abracadabra)", "t");
|
||||
expect("is_substring(abrac, abracadabra)", "t");
|
||||
expect("is_substring(dabra, abracadabra)", "t");
|
||||
expect("is_substring(cad, abracxadabra)", "");
|
||||
expect("is_substring(abrac, axbracadabra)", "");
|
||||
expect("is_substring(dabra, abracadabrxa)", "");
|
||||
}
|
||||
|
||||
TEST_F(EdifyTest, ifelse) {
|
||||
// ifelse function
|
||||
expect("ifelse(t, yes, no)", "yes");
|
||||
expect("ifelse(!t, yes, no)", "no");
|
||||
expect("ifelse(t, yes, abort())", "yes");
|
||||
expect("ifelse(!t, abort(), no)", "no");
|
||||
}
|
||||
|
||||
TEST_F(EdifyTest, if_statement) {
|
||||
// if "statements"
|
||||
expect("if t then yes else no endif", "yes");
|
||||
expect("if \"\" then yes else no endif", "no");
|
||||
expect("if \"\" then yes endif", "");
|
||||
expect("if \"\"; t then yes endif", "yes");
|
||||
}
|
||||
|
||||
TEST_F(EdifyTest, comparison) {
|
||||
// numeric comparisons
|
||||
expect("less_than_int(3, 14)", "t");
|
||||
expect("less_than_int(14, 3)", "");
|
||||
expect("less_than_int(x, 3)", "");
|
||||
expect("less_than_int(3, x)", "");
|
||||
expect("greater_than_int(3, 14)", "");
|
||||
expect("greater_than_int(14, 3)", "t");
|
||||
expect("greater_than_int(x, 3)", "");
|
||||
expect("greater_than_int(3, x)", "");
|
||||
}
|
||||
|
||||
TEST_F(EdifyTest, big_string) {
|
||||
expect(std::string(8192, 's'), std::string(8192, 's').c_str());
|
||||
}
|
||||
|
||||
TEST_F(EdifyTest, unknown_function) {
|
||||
const char* script1 = "unknown_function()";
|
||||
std::unique_ptr<Expr> expr;
|
||||
int error_count = 0;
|
||||
EXPECT_EQ(1, ParseString(script1, &expr, &error_count));
|
||||
EXPECT_EQ(1, error_count);
|
||||
|
||||
const char* script2 = "abc; unknown_function()";
|
||||
error_count = 0;
|
||||
EXPECT_EQ(1, ParseString(script2, &expr, &error_count));
|
||||
EXPECT_EQ(1, error_count);
|
||||
|
||||
const char* script3 = "unknown_function1() || yes";
|
||||
error_count = 0;
|
||||
EXPECT_EQ(1, ParseString(script3, &expr, &error_count));
|
||||
EXPECT_EQ(1, error_count);
|
||||
}
|
1113
tests/unit/host/imgdiff_test.cpp
Normal file
1113
tests/unit/host/imgdiff_test.cpp
Normal file
File diff suppressed because it is too large
Load diff
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, false);
|
||||
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 DISABLED_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(DISABLED_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(DISABLED_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(DISABLED_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, false);
|
||||
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(DISABLED_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(DISABLED_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(DISABLED_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);
|
||||
}
|
1227
tests/unit/updater_test.cpp
Normal file
1227
tests/unit/updater_test.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -12,13 +12,6 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
soong_namespace {
|
||||
imports: [
|
||||
"bootable/recovery/edify",
|
||||
"bootable/recovery/applypatch",
|
||||
],
|
||||
}
|
||||
|
||||
package {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
|
|
118
updater/Android.mk
Normal file
118
updater/Android.mk
Normal file
|
@ -0,0 +1,118 @@
|
|||
# Copyright 2009 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.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
tune2fs_static_libraries := \
|
||||
libext2_com_err \
|
||||
libext2_blkid \
|
||||
libext2_quota \
|
||||
libext2_uuid \
|
||||
libext2_e2p \
|
||||
libext2fs
|
||||
|
||||
updater_common_static_libraries := \
|
||||
libapplypatch \
|
||||
libbootloader_message \
|
||||
libbspatch \
|
||||
libedify \
|
||||
libotautil \
|
||||
libext4_utils \
|
||||
libdm \
|
||||
libfec \
|
||||
libfec_rs \
|
||||
libavb \
|
||||
libverity_tree \
|
||||
liblog \
|
||||
liblp \
|
||||
libselinux \
|
||||
libsparse \
|
||||
libsquashfs_utils \
|
||||
libbrotli \
|
||||
libbz \
|
||||
libziparchive \
|
||||
libz_stable \
|
||||
libbase \
|
||||
libcrypto_static \
|
||||
libcrypto_utils \
|
||||
libcutils \
|
||||
libutils
|
||||
|
||||
|
||||
# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function
|
||||
# named "Register_<libname>()". Here we emit a little C function that
|
||||
# gets #included by updater.cpp. It calls all those registration
|
||||
# functions.
|
||||
# $(1): the path to the register.inc file
|
||||
# $(2): a list of TARGET_RECOVERY_UPDATER_LIBS
|
||||
define generate-register-inc
|
||||
$(hide) mkdir -p $(dir $(1))
|
||||
$(hide) echo "" > $(1)
|
||||
$(hide) $(foreach lib,$(2),echo "extern void Register_$(lib)(void);" >> $(1);)
|
||||
$(hide) echo "void RegisterDeviceExtensions() {" >> $(1)
|
||||
$(hide) $(foreach lib,$(2),echo " Register_$(lib)();" >> $(1);)
|
||||
$(hide) echo "}" >> $(1)
|
||||
endef
|
||||
|
||||
|
||||
# updater (static executable)
|
||||
# ===============================
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := updater
|
||||
LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
|
||||
LOCAL_LICENSE_CONDITIONS := notice
|
||||
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
updater_main.cpp
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
$(LOCAL_PATH)/include
|
||||
|
||||
LOCAL_CFLAGS := \
|
||||
-Wall \
|
||||
-Werror
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libupdater_device \
|
||||
libupdater_core \
|
||||
$(TARGET_RECOVERY_UPDATER_LIBS) \
|
||||
$(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) \
|
||||
$(updater_common_static_libraries) \
|
||||
libfs_mgr \
|
||||
libtune2fs \
|
||||
$(tune2fs_static_libraries)
|
||||
|
||||
LOCAL_HEADER_LIBRARIES := libgtest_prod_headers
|
||||
|
||||
LOCAL_MODULE_CLASS := EXECUTABLES
|
||||
inc := $(call local-generated-sources-dir)/register.inc
|
||||
|
||||
# Devices can also add libraries to TARGET_RECOVERY_UPDATER_EXTRA_LIBS.
|
||||
# These libs are also linked in with updater, but we don't try to call
|
||||
# any sort of registration function for these. Use this variable for
|
||||
# any subsidiary static libraries required for your registered
|
||||
# extension libs.
|
||||
$(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS)
|
||||
$(inc) :
|
||||
$(call generate-register-inc,$@,$(libs))
|
||||
|
||||
LOCAL_GENERATED_SOURCES := $(inc)
|
||||
|
||||
inc :=
|
||||
|
||||
LOCAL_FORCE_STATIC_EXECUTABLE := true
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
Loading…
Reference in a new issue