Merge "Remove non-AB unittests" into main

This commit is contained in:
Treehugger Robot 2024-05-14 16:00:48 +00:00 committed by Gerrit Code Review
commit 2611111482
15 changed files with 20 additions and 4094 deletions

View file

@ -35,7 +35,10 @@ package {
// See: http://go/android-license-faq // See: http://go/android-license-faq
license { license {
name: "bootable_recovery_license", name: "bootable_recovery_license",
visibility: [":__subpackages__"], visibility: [
":__subpackages__",
"//bootable/deprecated-ota:__subpackages__",
],
license_kinds: [ license_kinds: [
"SPDX-license-identifier-Apache-2.0", "SPDX-license-identifier-Apache-2.0",
"SPDX-license-identifier-MIT", "SPDX-license-identifier-MIT",
@ -175,11 +178,9 @@ cc_binary {
required: [ required: [
"e2fsdroid.recovery", "e2fsdroid.recovery",
"init_recovery.rc", "init_recovery.rc",
"librecovery_ui_ext",
"minadbd", "minadbd",
"mke2fs.conf.recovery", "mke2fs.conf.recovery",
"mke2fs.recovery", "mke2fs.recovery",
"recovery_deps",
"ueventd.rc.recovery", "ueventd.rc.recovery",
], ],
} }

View file

@ -84,5 +84,3 @@ endif
include $(BUILD_PHONY_PACKAGE) include $(BUILD_PHONY_PACKAGE)
include \
$(LOCAL_PATH)/updater/Android.mk \

View file

@ -12,6 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
soong_namespace {
imports: [
"bootable/recovery/edify",
],
}
package { package {
default_applicable_licenses: ["bootable_recovery_applypatch_license"], default_applicable_licenses: ["bootable_recovery_applypatch_license"],
} }

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
soong_namespace {}
package { package {
// See: http://go/android-license-faq // See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import // A large-scale-change added 'default_applicable_licenses' to import

View file

@ -98,5 +98,6 @@ cc_library_static {
"//bootable/recovery/install", "//bootable/recovery/install",
"//bootable/recovery/minadbd", "//bootable/recovery/minadbd",
"//bootable/recovery/tests", "//bootable/recovery/tests",
"//bootable/deprecated-ota:__subpackages__",
], ],
} }

View file

@ -62,9 +62,6 @@ cc_defaults {
// libapplypatch, libapplypatch_modes // libapplypatch, libapplypatch_modes
libapplypatch_static_libs = [ libapplypatch_static_libs = [
"libapplypatch_modes",
"libapplypatch",
"libedify",
"libotautil", "libotautil",
"libbsdiff", "libbsdiff",
"libbspatch", "libbspatch",
@ -126,16 +123,10 @@ cc_test {
defaults: [ defaults: [
"recovery_test_defaults", "recovery_test_defaults",
"libupdater_defaults",
"libupdater_device_defaults",
], ],
test_suites: ["device-tests"], test_suites: ["device-tests"],
tidy_timeout_srcs: [
"unit/commands_test.cpp",
],
srcs: [ srcs: [
"unit/*.cpp", "unit/*.cpp",
], ],
@ -153,8 +144,6 @@ cc_test {
"libminui", "libminui",
"librecovery_utils", "librecovery_utils",
"libotautil", "libotautil",
"libupdater_device",
"libupdater_core",
"libupdate_verifier", "libupdate_verifier",
"libprotobuf-cpp-lite", "libprotobuf-cpp-lite",
@ -191,11 +180,6 @@ cc_test_host {
defaults: [ defaults: [
"recovery_test_defaults", "recovery_test_defaults",
"libupdater_defaults",
],
tidy_timeout_srcs: [
"unit/host/imgdiff_test.cpp",
], ],
srcs: [ srcs: [
@ -203,9 +187,6 @@ cc_test_host {
], ],
static_libs: [ static_libs: [
"libupdater_host",
"libupdater_core",
"libimgdiff",
"libbsdiff", "libbsdiff",
"libdivsufsort64", "libdivsufsort64",
"libdivsufsort", "libdivsufsort",

View file

@ -1,198 +0,0 @@
/*
* 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" }));
}

View file

@ -1,290 +0,0 @@
/*
* 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));
}

View file

@ -1,554 +0,0 @@
/*
* 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());
}

View file

@ -1,167 +0,0 @@
/*
* 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);
}

File diff suppressed because it is too large Load diff

View file

@ -1,403 +0,0 @@
/*
* 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);
}

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
soong_namespace {
imports: [
"bootable/recovery/edify",
"bootable/recovery/applypatch",
],
}
package { package {
// See: http://go/android-license-faq // See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import // A large-scale-change added 'default_applicable_licenses' to import

View file

@ -1,118 +0,0 @@
# 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)