summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorEthan Yonker <dees_troy@teamw.in>2017-09-08 13:50:54 +0200
committerEthan Yonker <dees_troy@teamw.in>2017-09-08 14:14:59 +0200
commit8373cfe28cf1b5ad758faa1d502e21787c3665e4 (patch)
tree2f567c52cd6f89e1052481497d236b58d230b7e2 /tests
parentDO NOT MERGE Android 8.0 stuff (diff)
parentrelease-request-f4ecf242-5d1c-45e0-8c7c-ede48d1a9e82-for-git_oc-release-4111650 snap-temp-L02200000075283731 (diff)
downloadandroid_bootable_recovery-8373cfe28cf1b5ad758faa1d502e21787c3665e4.tar
android_bootable_recovery-8373cfe28cf1b5ad758faa1d502e21787c3665e4.tar.gz
android_bootable_recovery-8373cfe28cf1b5ad758faa1d502e21787c3665e4.tar.bz2
android_bootable_recovery-8373cfe28cf1b5ad758faa1d502e21787c3665e4.tar.lz
android_bootable_recovery-8373cfe28cf1b5ad758faa1d502e21787c3665e4.tar.xz
android_bootable_recovery-8373cfe28cf1b5ad758faa1d502e21787c3665e4.tar.zst
android_bootable_recovery-8373cfe28cf1b5ad758faa1d502e21787c3665e4.zip
Diffstat (limited to 'tests')
-rw-r--r--tests/Android.mk169
-rw-r--r--tests/common/component_test_util.h43
-rw-r--r--tests/common/test_constants.h26
-rw-r--r--tests/component/applypatch_test.cpp609
-rw-r--r--tests/component/bootloader_message_test.cpp207
-rw-r--r--tests/component/edify_test.cpp170
-rw-r--r--tests/component/imgdiff_test.cpp585
-rw-r--r--tests/component/install_test.cpp228
-rw-r--r--tests/component/sideload_test.cpp21
-rw-r--r--tests/component/uncrypt_test.cpp192
-rw-r--r--tests/component/updater_test.cpp609
-rw-r--r--tests/component/verifier_test.cpp237
-rw-r--r--tests/manual/recovery_test.cpp224
-rw-r--r--tests/testdata/bonus.filebin0 -> 557334 bytes
-rw-r--r--tests/testdata/boot.imgbin0 -> 783655 bytes
-rw-r--r--tests/testdata/otasigned_v1.zip (renamed from tests/testdata/otasigned.zip)bin4009 -> 4009 bytes
-rw-r--r--tests/testdata/otasigned_v2.zip (renamed from tests/testdata/otasigned_f4.zip)bin5195 -> 5195 bytes
-rw-r--r--tests/testdata/otasigned_v3.zip (renamed from tests/testdata/otasigned_sha256.zip)bin5326 -> 5326 bytes
-rw-r--r--tests/testdata/otasigned_v4.zip (renamed from tests/testdata/otasigned_f4_sha256.zip)bin5319 -> 5319 bytes
-rw-r--r--tests/testdata/otasigned_v5.zip (renamed from tests/testdata/otasigned_ecdsa_sha256.zip)bin3085 -> 3085 bytes
-rw-r--r--tests/testdata/recovery-from-boot-with-bonus.pbin0 -> 381615 bytes
-rw-r--r--tests/testdata/recovery-from-boot.pbin0 -> 5404 bytes
-rw-r--r--tests/testdata/recovery.imgbin0 -> 529707 bytes
-rw-r--r--tests/testdata/testkey_v1.pk8 (renamed from tests/testdata/testkey.pk8)bin1217 -> 1217 bytes
-rw-r--r--tests/testdata/testkey_v1.txt (renamed from tests/testdata/test_key_e3.txt)0
-rw-r--r--tests/testdata/testkey_v1.x509.pem (renamed from tests/testdata/testkey.x509.pem)0
-rw-r--r--tests/testdata/testkey_v2.pk8 (renamed from tests/testdata/test_f4.pk8)bin1217 -> 1217 bytes
-rw-r--r--tests/testdata/testkey_v2.txt (renamed from tests/testdata/test_key_f4.txt)0
-rw-r--r--tests/testdata/testkey_v2.x509.pem (renamed from tests/testdata/test_f4.x509.pem)0
l---------tests/testdata/testkey_v3.pk81
-rw-r--r--tests/testdata/testkey_v3.txt1
-rw-r--r--tests/testdata/testkey_v3.x509.pem (renamed from tests/testdata/testkey_sha256.x509.pem)0
l---------tests/testdata/testkey_v4.pk81
-rw-r--r--tests/testdata/testkey_v4.txt1
-rw-r--r--tests/testdata/testkey_v4.x509.pem (renamed from tests/testdata/test_f4_sha256.x509.pem)0
-rw-r--r--tests/testdata/testkey_v5.pk8 (renamed from tests/testdata/testkey_ecdsa.pk8)bin138 -> 138 bytes
-rw-r--r--tests/testdata/testkey_v5.txt (renamed from tests/testdata/test_key_ec.txt)0
-rw-r--r--tests/testdata/testkey_v5.x509.pem (renamed from tests/testdata/testkey_ecdsa.x509.pem)0
-rw-r--r--tests/testdata/ziptest_dummy-update.zipbin0 -> 1090065 bytes
-rw-r--r--tests/testdata/ziptest_valid.zipbin0 -> 758 bytes
-rw-r--r--tests/unit/asn1_decoder_test.cpp397
-rw-r--r--tests/unit/dirutil_test.cpp150
-rw-r--r--tests/unit/locale_test.cpp18
-rw-r--r--tests/unit/recovery_test.cpp92
-rw-r--r--tests/unit/sysutil_test.cpp140
-rw-r--r--tests/unit/zip_test.cpp90
-rw-r--r--tests/unit/ziputil_test.cpp191
47 files changed, 3666 insertions, 736 deletions
diff --git a/tests/Android.mk b/tests/Android.mk
index 4fc92fa95..e4f252127 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -24,52 +24,187 @@ endif
# Unit tests
include $(CLEAR_VARS)
-LOCAL_CLANG := true
+LOCAL_CFLAGS := -Werror
LOCAL_MODULE := recovery_unit_test
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
LOCAL_STATIC_LIBRARIES := \
libverifier \
- libminui
+ libminui \
+ libotautil \
+ libziparchive \
+ libutils \
+ libz \
+ libselinux \
+ libbase
+
+LOCAL_SRC_FILES := \
+ unit/asn1_decoder_test.cpp \
+ unit/dirutil_test.cpp \
+ unit/locale_test.cpp \
+ unit/sysutil_test.cpp \
+ unit/zip_test.cpp \
+ unit/ziputil_test.cpp
-LOCAL_SRC_FILES := unit/asn1_decoder_test.cpp
-LOCAL_SRC_FILES += unit/recovery_test.cpp
-LOCAL_SRC_FILES += unit/locale_test.cpp
LOCAL_C_INCLUDES := $(RECOVERY_PATH)
LOCAL_SHARED_LIBRARIES := liblog
include $(BUILD_NATIVE_TEST)
-# Component tests
+# Manual tests
include $(CLEAR_VARS)
LOCAL_CLANG := true
-LOCAL_CFLAGS += -Wno-unused-parameter
+LOCAL_CFLAGS := -Werror
+LOCAL_MODULE := recovery_manual_test
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+LOCAL_STATIC_LIBRARIES := \
+ libminui \
+ libbase
+
+LOCAL_SRC_FILES := manual/recovery_test.cpp
+LOCAL_SHARED_LIBRARIES := \
+ liblog \
+ libpng
+
+resource_files := $(call find-files-in-subdirs, bootable/recovery, \
+ "*_text.png", \
+ res-mdpi/images \
+ res-hdpi/images \
+ res-xhdpi/images \
+ res-xxhdpi/images \
+ res-xxxhdpi/images \
+ )
+
+# The resource image files that will go to $OUT/data/nativetest/recovery.
+testimage_out_path := $(TARGET_OUT_DATA)/nativetest/recovery
+GEN := $(addprefix $(testimage_out_path)/, $(resource_files))
+
+$(GEN): PRIVATE_PATH := $(LOCAL_PATH)
+$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
+$(GEN): $(testimage_out_path)/% : bootable/recovery/%
+ $(transform-generated-source)
+LOCAL_GENERATED_SOURCES += $(GEN)
+
+include $(BUILD_NATIVE_TEST)
+
+# Component tests
+include $(CLEAR_VARS)
+LOCAL_CFLAGS := \
+ -Werror \
+ -D_FILE_OFFSET_BITS=64
+
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+ifeq ($(AB_OTA_UPDATER),true)
+LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
+endif
+
LOCAL_MODULE := recovery_component_test
LOCAL_C_INCLUDES := $(RECOVERY_PATH)
LOCAL_SRC_FILES := \
- component/verifier_test.cpp \
- component/applypatch_test.cpp
+ component/applypatch_test.cpp \
+ component/bootloader_message_test.cpp \
+ component/edify_test.cpp \
+ component/imgdiff_test.cpp \
+ component/install_test.cpp \
+ component/sideload_test.cpp \
+ component/uncrypt_test.cpp \
+ component/updater_test.cpp \
+ component/verifier_test.cpp
+
LOCAL_FORCE_STATIC_EXECUTABLE := true
+
+tune2fs_static_libraries := \
+ libext2_com_err \
+ libext2_blkid \
+ libext2_quota \
+ libext2_uuid \
+ libext2_e2p \
+ libext2fs
+
LOCAL_STATIC_LIBRARIES := \
+ libapplypatch_modes \
libapplypatch \
+ libedify \
+ libimgdiff \
+ libimgpatch \
+ libbsdiff \
+ libbspatch \
libotafault \
- libmtdutils \
- libbase \
+ librecovery \
+ libupdater \
+ libbootloader_message \
libverifier \
- libcrypto_static \
- libminui \
- libminzip \
- libcutils \
+ libotautil \
+ libmounts \
+ libdivsufsort \
+ libdivsufsort64 \
+ libfs_mgr \
+ liblog \
+ libvintf_recovery \
+ libvintf \
+ libtinyxml2 \
+ libselinux \
+ libext4_utils \
+ libsparse \
+ libcrypto_utils \
+ libcrypto \
libbz \
+ libziparchive \
+ libutils \
libz \
- libc
+ libbase \
+ libtune2fs \
+ libfec \
+ libfec_rs \
+ libsquashfs_utils \
+ libcutils \
+ $(tune2fs_static_libraries)
-testdata_out_path := $(TARGET_OUT_DATA_NATIVE_TESTS)/recovery
testdata_files := $(call find-subdir-files, testdata/*)
+# The testdata files that will go to $OUT/data/nativetest/recovery.
+testdata_out_path := $(TARGET_OUT_DATA)/nativetest/recovery
GEN := $(addprefix $(testdata_out_path)/, $(testdata_files))
$(GEN): PRIVATE_PATH := $(LOCAL_PATH)
$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
$(GEN): $(testdata_out_path)/% : $(LOCAL_PATH)/%
$(transform-generated-source)
LOCAL_GENERATED_SOURCES += $(GEN)
+
+# A copy of the testdata to be packed into continuous_native_tests.zip.
+testdata_continuous_zip_prefix := \
+ $(call intermediates-dir-for,PACKAGING,recovery_component_test)/DATA
+testdata_continuous_zip_path := $(testdata_continuous_zip_prefix)/nativetest/recovery
+GEN := $(addprefix $(testdata_continuous_zip_path)/, $(testdata_files))
+$(GEN): PRIVATE_PATH := $(LOCAL_PATH)
+$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
+$(GEN): $(testdata_continuous_zip_path)/% : $(LOCAL_PATH)/%
+ $(transform-generated-source)
+LOCAL_GENERATED_SOURCES += $(GEN)
+LOCAL_PICKUP_FILES := $(testdata_continuous_zip_prefix)
+
include $(BUILD_NATIVE_TEST)
+
+# Host tests
+include $(CLEAR_VARS)
+LOCAL_CFLAGS := -Werror
+LOCAL_MODULE := recovery_host_test
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_C_INCLUDES := bootable/recovery
+LOCAL_SRC_FILES := \
+ component/imgdiff_test.cpp
+LOCAL_STATIC_LIBRARIES := \
+ libimgdiff \
+ libimgpatch \
+ libbsdiff \
+ libbspatch \
+ libziparchive \
+ libutils \
+ libbase \
+ libcrypto \
+ libbz \
+ libdivsufsort64 \
+ libdivsufsort \
+ libz
+LOCAL_SHARED_LIBRARIES := \
+ liblog
+include $(BUILD_HOST_NATIVE_TEST)
diff --git a/tests/common/component_test_util.h b/tests/common/component_test_util.h
new file mode 100644
index 000000000..3fee32d62
--- /dev/null
+++ b/tests/common/component_test_util.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef _COMPONENT_TEST_UTIL_H
+#define _COMPONENT_TEST_UTIL_H
+
+#include <string>
+
+#include <android-base/properties.h>
+#include <fs_mgr.h>
+
+// Check if the /misc entry exists in the fstab.
+static bool parse_misc() {
+ std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(),
+ fs_mgr_free_fstab);
+ if (!fstab) {
+ GTEST_LOG_(INFO) << "Failed to read default fstab";
+ return false;
+ }
+
+ fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc");
+ if (record == nullptr) {
+ GTEST_LOG_(INFO) << "Failed to find /misc in fstab.";
+ return false;
+ }
+ return true;
+}
+
+#endif //_COMPONENT_TEST_UTIL_H
+
diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h
index 3490f6805..f6b6922a4 100644
--- a/tests/common/test_constants.h
+++ b/tests/common/test_constants.h
@@ -13,13 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
#ifndef _OTA_TEST_CONSTANTS_H
#define _OTA_TEST_CONSTANTS_H
-#if defined(__LP64__)
-#define NATIVE_TEST_PATH "/nativetest64"
-#else
-#define NATIVE_TEST_PATH "/nativetest"
-#endif
+#include <stdlib.h>
+
+// Zip entries in ziptest_valid.zip.
+static const std::string kATxtContents("abcdefghabcdefgh\n");
+static const std::string kBTxtContents("abcdefgh\n");
+static const std::string kCTxtContents("abcdefghabcdefgh\n");
+static const std::string kDTxtContents("abcdefgh\n");
+
+// echo -n -e "abcdefghabcdefgh\n" | sha1sum
+static const std::string kATxtSha1Sum("32c96a03dc8cd20097940f351bca6261ee5a1643");
+// echo -n -e "abcdefgh\n" | sha1sum
+static const std::string kBTxtSha1Sum("e414af7161c9554089f4106d6f1797ef14a73666");
+
+static const char* data_root = getenv("ANDROID_DATA");
+
+static std::string from_testdata_base(const std::string& fname) {
+ return std::string(data_root) + "/nativetest/recovery/testdata/" + fname;
+}
-#endif
+#endif // _OTA_TEST_CONSTANTS_H
diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp
index b44ddd17c..5cba68f8a 100644
--- a/tests/component/applypatch_test.cpp
+++ b/tests/component/applypatch_test.cpp
@@ -23,176 +23,155 @@
#include <sys/types.h>
#include <time.h>
+#include <memory>
#include <string>
+#include <vector>
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android-base/test_utils.h>
+#include <openssl/sha.h>
#include "applypatch/applypatch.h"
+#include "applypatch/applypatch_modes.h"
#include "common/test_constants.h"
-#include "openssl/sha.h"
#include "print_sha1.h"
-static const std::string DATA_PATH = getenv("ANDROID_DATA");
-static const std::string TESTDATA_PATH = "/recovery/testdata";
-static const std::string WORK_FS = "/data";
+static void sha1sum(const std::string& fname, std::string* sha1, size_t* fsize = nullptr) {
+ ASSERT_NE(nullptr, sha1);
-static std::string sha1sum(const std::string& fname) {
- uint8_t digest[SHA_DIGEST_LENGTH];
- std::string data;
- android::base::ReadFileToString(fname, &data);
+ std::string data;
+ ASSERT_TRUE(android::base::ReadFileToString(fname, &data));
- SHA1((const uint8_t*)data.c_str(), data.size(), digest);
- return print_sha1(digest);
-}
+ if (fsize != nullptr) {
+ *fsize = data.size();
+ }
-static void mangle_file(const std::string& fname) {
- FILE* fh = fopen(&fname[0], "w");
- int r;
- for (int i=0; i < 1024; i++) {
- r = rand();
- fwrite(&r, sizeof(short), 1, fh);
- }
- fclose(fh);
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest);
+ *sha1 = print_sha1(digest);
}
-static bool file_cmp(std::string& f1, std::string& f2) {
- std::string c1;
- std::string c2;
- android::base::ReadFileToString(f1, &c1);
- android::base::ReadFileToString(f2, &c2);
- return c1 == c2;
+static void mangle_file(const std::string& fname) {
+ std::string content;
+ content.reserve(1024);
+ for (size_t i = 0; i < 1024; i++) {
+ content[i] = rand() % 256;
+ }
+ ASSERT_TRUE(android::base::WriteStringToFile(content, fname));
}
-static std::string from_testdata_base(const std::string fname) {
- return android::base::StringPrintf("%s%s%s/%s",
- &DATA_PATH[0],
- &NATIVE_TEST_PATH[0],
- &TESTDATA_PATH[0],
- &fname[0]);
+static bool file_cmp(const std::string& f1, const std::string& f2) {
+ std::string c1;
+ android::base::ReadFileToString(f1, &c1);
+ std::string c2;
+ android::base::ReadFileToString(f2, &c2);
+ return c1 == c2;
}
class ApplyPatchTest : public ::testing::Test {
- public:
- static void SetUpTestCase() {
- // set up files
- old_file = from_testdata_base("old.file");
- new_file = from_testdata_base("new.file");
- patch_file = from_testdata_base("patch.bsdiff");
- rand_file = "/cache/applypatch_test_rand.file";
- cache_file = "/cache/saved.file";
-
- // write stuff to rand_file
- android::base::WriteStringToFile("hello", rand_file);
-
- // set up SHA constants
- old_sha1 = sha1sum(old_file);
- new_sha1 = sha1sum(new_file);
- srand(time(NULL));
- bad_sha1_a = android::base::StringPrintf("%040x", rand());
- bad_sha1_b = android::base::StringPrintf("%040x", rand());
-
- struct stat st;
- stat(&new_file[0], &st);
- new_size = st.st_size;
- }
-
- static std::string old_file;
- static std::string new_file;
- static std::string rand_file;
- static std::string cache_file;
- static std::string patch_file;
-
- static std::string old_sha1;
- static std::string new_sha1;
- static std::string bad_sha1_a;
- static std::string bad_sha1_b;
-
- static size_t new_size;
+ public:
+ static void SetUpTestCase() {
+ // set up files
+ old_file = from_testdata_base("old.file");
+ new_file = from_testdata_base("new.file");
+ patch_file = from_testdata_base("patch.bsdiff");
+ rand_file = "/cache/applypatch_test_rand.file";
+ cache_file = "/cache/saved.file";
+
+ // write stuff to rand_file
+ ASSERT_TRUE(android::base::WriteStringToFile("hello", rand_file));
+
+ // set up SHA constants
+ sha1sum(old_file, &old_sha1, &old_size);
+ sha1sum(new_file, &new_sha1, &new_size);
+ srand(time(nullptr));
+ bad_sha1_a = android::base::StringPrintf("%040x", rand());
+ bad_sha1_b = android::base::StringPrintf("%040x", rand());
+ }
+
+ static std::string old_file;
+ static std::string new_file;
+ static std::string rand_file;
+ static std::string cache_file;
+ static std::string patch_file;
+
+ static std::string old_sha1;
+ static std::string new_sha1;
+ static std::string bad_sha1_a;
+ static std::string bad_sha1_b;
+
+ static size_t old_size;
+ static size_t new_size;
};
std::string ApplyPatchTest::old_file;
std::string ApplyPatchTest::new_file;
-static void cp(std::string src, std::string tgt) {
- std::string cmd = android::base::StringPrintf("cp %s %s",
- &src[0],
- &tgt[0]);
- system(&cmd[0]);
+static void cp(const std::string& src, const std::string& tgt) {
+ std::string cmd = "cp " + src + " " + tgt;
+ system(cmd.c_str());
}
static void backup_old() {
- cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file);
+ cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file);
}
static void restore_old() {
- cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file);
+ cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file);
}
class ApplyPatchCacheTest : public ApplyPatchTest {
- public:
- virtual void SetUp() {
- backup_old();
- }
-
- virtual void TearDown() {
- restore_old();
- }
+ public:
+ virtual void SetUp() {
+ backup_old();
+ }
+
+ virtual void TearDown() {
+ restore_old();
+ }
};
class ApplyPatchFullTest : public ApplyPatchCacheTest {
- public:
- static void SetUpTestCase() {
- ApplyPatchTest::SetUpTestCase();
- unsigned long free_kb = FreeSpaceForFile(&WORK_FS[0]);
- ASSERT_GE(free_kb * 1024, new_size * 3 / 2);
- output_f = new TemporaryFile();
- output_loc = std::string(output_f->path);
-
- struct FileContents fc;
-
- ASSERT_EQ(0, LoadFileContents(&rand_file[0], &fc));
- Value* patch1 = new Value();
- patch1->type = VAL_BLOB;
- patch1->size = fc.data.size();
- patch1->data = static_cast<char*>(malloc(fc.data.size()));
- memcpy(patch1->data, fc.data.data(), fc.data.size());
- patches.push_back(patch1);
-
- ASSERT_EQ(0, LoadFileContents(&patch_file[0], &fc));
- Value* patch2 = new Value();
- patch2->type = VAL_BLOB;
- patch2->size = fc.st.st_size;
- patch2->data = static_cast<char*>(malloc(fc.data.size()));
- memcpy(patch2->data, fc.data.data(), fc.data.size());
- patches.push_back(patch2);
- }
- static void TearDownTestCase() {
- delete output_f;
- for (auto it = patches.begin(); it != patches.end(); ++it) {
- free((*it)->data);
- delete *it;
- }
- patches.clear();
- }
-
- static std::vector<Value*> patches;
- static TemporaryFile* output_f;
- static std::string output_loc;
+ public:
+ static void SetUpTestCase() {
+ ApplyPatchTest::SetUpTestCase();
+
+ output_f = new TemporaryFile();
+ output_loc = std::string(output_f->path);
+
+ struct FileContents fc;
+
+ ASSERT_EQ(0, LoadFileContents(&rand_file[0], &fc));
+ patches.push_back(
+ std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end())));
+
+ ASSERT_EQ(0, LoadFileContents(&patch_file[0], &fc));
+ patches.push_back(
+ std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end())));
+ }
+
+ static void TearDownTestCase() {
+ delete output_f;
+ patches.clear();
+ }
+
+ static std::vector<std::unique_ptr<Value>> patches;
+ static TemporaryFile* output_f;
+ static std::string output_loc;
};
class ApplyPatchDoubleCacheTest : public ApplyPatchFullTest {
- public:
- virtual void SetUp() {
- ApplyPatchCacheTest::SetUp();
- cp(cache_file, "/cache/reallysaved.file");
- }
-
- virtual void TearDown() {
- cp("/cache/reallysaved.file", cache_file);
- ApplyPatchCacheTest::TearDown();
- }
+ public:
+ virtual void SetUp() {
+ ApplyPatchCacheTest::SetUp();
+ cp(cache_file, "/cache/reallysaved.file");
+ }
+
+ virtual void TearDown() {
+ cp("/cache/reallysaved.file", cache_file);
+ ApplyPatchCacheTest::TearDown();
+ }
};
std::string ApplyPatchTest::rand_file;
@@ -202,191 +181,263 @@ std::string ApplyPatchTest::old_sha1;
std::string ApplyPatchTest::new_sha1;
std::string ApplyPatchTest::bad_sha1_a;
std::string ApplyPatchTest::bad_sha1_b;
-
+size_t ApplyPatchTest::old_size;
size_t ApplyPatchTest::new_size;
-std::vector<Value*> ApplyPatchFullTest::patches;
+std::vector<std::unique_ptr<Value>> ApplyPatchFullTest::patches;
TemporaryFile* ApplyPatchFullTest::output_f;
std::string ApplyPatchFullTest::output_loc;
+TEST_F(ApplyPatchTest, CheckModeSkip) {
+ std::vector<std::string> sha1s;
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
+}
+
TEST_F(ApplyPatchTest, CheckModeSingle) {
- char* s = &old_sha1[0];
- ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s));
+ std::vector<std::string> sha1s = { old_sha1 };
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchTest, CheckModeMultiple) {
- char* argv[3] = {
- &bad_sha1_a[0],
- &old_sha1[0],
- &bad_sha1_b[0]
- };
- ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv));
+ std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchTest, CheckModeFailure) {
- char* argv[2] = {
- &bad_sha1_a[0],
- &bad_sha1_b[0]
- };
- ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv));
+ std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
+ ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
+}
+
+TEST_F(ApplyPatchTest, CheckModeEmmcTarget) {
+ // EMMC:old_file:size:sha1 should pass the check.
+ std::string src_file =
+ "EMMC:" + old_file + ":" + std::to_string(old_size) + ":" + old_sha1;
+ std::vector<std::string> sha1s;
+ ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
+
+ // EMMC:old_file:(size-1):sha1:(size+1):sha1 should fail the check.
+ src_file = "EMMC:" + old_file + ":" + std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+ std::to_string(old_size + 1) + ":" + old_sha1;
+ ASSERT_EQ(1, applypatch_check(src_file.c_str(), sha1s));
+
+ // EMMC:old_file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check.
+ src_file = "EMMC:" + old_file + ":" +
+ std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+ std::to_string(old_size) + ":" + old_sha1 + ":" +
+ std::to_string(old_size + 1) + ":" + old_sha1;
+ ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
+
+ // EMMC:old_file:(size+1):sha1:(size-1):sha1:size:sha1 should pass the check.
+ src_file = "EMMC:" + old_file + ":" +
+ std::to_string(old_size + 1) + ":" + old_sha1 + ":" +
+ std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+ std::to_string(old_size) + ":" + old_sha1;
+ ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
+
+ // EMMC:new_file:(size+1):old_sha1:(size-1):old_sha1:size:old_sha1:size:new_sha1
+ // should pass the check.
+ src_file = "EMMC:" + new_file + ":" +
+ std::to_string(old_size + 1) + ":" + old_sha1 + ":" +
+ std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+ std::to_string(old_size) + ":" + old_sha1 + ":" +
+ std::to_string(new_size) + ":" + new_sha1;
+ ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
}
TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSingle) {
- mangle_file(old_file);
- char* s = &old_sha1[0];
- ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s));
+ mangle_file(old_file);
+ std::vector<std::string> sha1s = { old_sha1 };
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedMultiple) {
- mangle_file(old_file);
- char* argv[3] = {
- &bad_sha1_a[0],
- &old_sha1[0],
- &bad_sha1_b[0]
- };
- ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv));
+ mangle_file(old_file);
+ std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedFailure) {
- mangle_file(old_file);
- char* argv[2] = {
- &bad_sha1_a[0],
- &bad_sha1_b[0]
- };
- ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv));
+ mangle_file(old_file);
+ std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
+ ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchCacheTest, CheckCacheMissingSingle) {
- unlink(&old_file[0]);
- char* s = &old_sha1[0];
- ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s));
+ unlink(&old_file[0]);
+ std::vector<std::string> sha1s = { old_sha1 };
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchCacheTest, CheckCacheMissingMultiple) {
- unlink(&old_file[0]);
- char* argv[3] = {
- &bad_sha1_a[0],
- &old_sha1[0],
- &bad_sha1_b[0]
- };
- ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv));
+ unlink(&old_file[0]);
+ std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchCacheTest, CheckCacheMissingFailure) {
- unlink(&old_file[0]);
- char* argv[2] = {
- &bad_sha1_a[0],
- &bad_sha1_b[0]
- };
- ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv));
+ unlink(&old_file[0]);
+ std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
+ ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
+}
+
+TEST(ApplyPatchModesTest, InvalidArgs) {
+ // At least two args (including the filename).
+ ASSERT_EQ(2, applypatch_modes(1, (const char* []){ "applypatch" }));
+
+ // Unrecognized args.
+ ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-x" }));
}
-TEST_F(ApplyPatchFullTest, ApplyInPlace) {
- std::vector<char*> sha1s;
- sha1s.push_back(&bad_sha1_a[0]);
- sha1s.push_back(&old_sha1[0]);
-
- int ap_result = applypatch(&old_file[0],
- "-",
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_EQ(0, ap_result);
- ASSERT_TRUE(file_cmp(old_file, new_file));
- // reapply, applypatch is idempotent so it should succeed
- ap_result = applypatch(&old_file[0],
- "-",
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_EQ(0, ap_result);
- ASSERT_TRUE(file_cmp(old_file, new_file));
+TEST(ApplyPatchModesTest, PatchModeEmmcTarget) {
+ std::string boot_img = from_testdata_base("boot.img");
+ size_t boot_img_size;
+ std::string boot_img_sha1;
+ sha1sum(boot_img, &boot_img_sha1, &boot_img_size);
+
+ std::string recovery_img = from_testdata_base("recovery.img");
+ size_t size;
+ std::string recovery_img_sha1;
+ sha1sum(recovery_img, &recovery_img_sha1, &size);
+ std::string recovery_img_size = std::to_string(size);
+
+ std::string bonus_file = from_testdata_base("bonus.file");
+
+ // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
+ TemporaryFile tmp1;
+ std::string src_file =
+ "EMMC:" + boot_img + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1;
+ std::string tgt_file = "EMMC:" + std::string(tmp1.path);
+ std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p");
+ std::vector<const char*> args = {
+ "applypatch",
+ "-b",
+ bonus_file.c_str(),
+ src_file.c_str(),
+ tgt_file.c_str(),
+ recovery_img_sha1.c_str(),
+ recovery_img_size.c_str(),
+ patch.c_str()
+ };
+ ASSERT_EQ(0, applypatch_modes(args.size(), args.data()));
+
+ // applypatch <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
+ TemporaryFile tmp2;
+ patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p");
+ tgt_file = "EMMC:" + std::string(tmp2.path);
+ std::vector<const char*> args2 = {
+ "applypatch",
+ src_file.c_str(),
+ tgt_file.c_str(),
+ recovery_img_sha1.c_str(),
+ recovery_img_size.c_str(),
+ patch.c_str()
+ };
+ ASSERT_EQ(0, applypatch_modes(args2.size(), args2.data()));
+
+ // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> \
+ // <src-sha1-fake>:<patch1> <src-sha1>:<patch2>
+ TemporaryFile tmp3;
+ tgt_file = "EMMC:" + std::string(tmp3.path);
+ std::string bad_sha1_a = android::base::StringPrintf("%040x", rand());
+ std::string bad_sha1_b = android::base::StringPrintf("%040x", rand());
+ std::string patch1 = bad_sha1_a + ":" + from_testdata_base("recovery-from-boot.p");
+ std::string patch2 = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p");
+ std::string patch3 = bad_sha1_b + ":" + from_testdata_base("recovery-from-boot.p");
+ std::vector<const char*> args3 = {
+ "applypatch",
+ "-b",
+ bonus_file.c_str(),
+ src_file.c_str(),
+ tgt_file.c_str(),
+ recovery_img_sha1.c_str(),
+ recovery_img_size.c_str(),
+ patch1.c_str(),
+ patch2.c_str(),
+ patch3.c_str()
+ };
+ ASSERT_EQ(0, applypatch_modes(args3.size(), args3.data()));
}
-TEST_F(ApplyPatchFullTest, ApplyInNewLocation) {
- std::vector<char*> sha1s;
- sha1s.push_back(&bad_sha1_a[0]);
- sha1s.push_back(&old_sha1[0]);
- int ap_result = applypatch(&old_file[0],
- &output_loc[0],
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_EQ(0, ap_result);
- ASSERT_TRUE(file_cmp(output_loc, new_file));
- ap_result = applypatch(&old_file[0],
- &output_loc[0],
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_EQ(0, ap_result);
- ASSERT_TRUE(file_cmp(output_loc, new_file));
+TEST(ApplyPatchModesTest, PatchModeInvalidArgs) {
+ // Invalid bonus file.
+ ASSERT_NE(0, applypatch_modes(3, (const char* []){ "applypatch", "-b", "/doesntexist" }));
+
+ std::string bonus_file = from_testdata_base("bonus.file");
+ // With bonus file, but missing args.
+ ASSERT_EQ(2, applypatch_modes(3, (const char* []){ "applypatch", "-b", bonus_file.c_str() }));
+
+ std::string boot_img = from_testdata_base("boot.img");
+ size_t boot_img_size;
+ std::string boot_img_sha1;
+ sha1sum(boot_img, &boot_img_sha1, &boot_img_size);
+
+ std::string recovery_img = from_testdata_base("recovery.img");
+ size_t size;
+ std::string recovery_img_sha1;
+ sha1sum(recovery_img, &recovery_img_sha1, &size);
+ std::string recovery_img_size = std::to_string(size);
+
+ // Bonus file is not supported in flash mode.
+ // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size>
+ TemporaryFile tmp4;
+ std::vector<const char*> args4 = {
+ "applypatch",
+ "-b",
+ bonus_file.c_str(),
+ boot_img.c_str(),
+ tmp4.path,
+ recovery_img_sha1.c_str(),
+ recovery_img_size.c_str()
+ };
+ ASSERT_NE(0, applypatch_modes(args4.size(), args4.data()));
+
+ // Failed to parse patch args.
+ TemporaryFile tmp5;
+ std::string bad_arg1 =
+ "invalid-sha1:filename" + from_testdata_base("recovery-from-boot-with-bonus.p");
+ std::vector<const char*> args5 = {
+ "applypatch",
+ boot_img.c_str(),
+ tmp5.path,
+ recovery_img_sha1.c_str(),
+ recovery_img_size.c_str(),
+ bad_arg1.c_str()
+ };
+ ASSERT_NE(0, applypatch_modes(args5.size(), args5.data()));
+
+ // Target size cannot be zero.
+ TemporaryFile tmp6;
+ std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p");
+ std::vector<const char*> args6 = {
+ "applypatch",
+ boot_img.c_str(),
+ tmp6.path,
+ recovery_img_sha1.c_str(),
+ "0", // target size
+ patch.c_str()
+ };
+ ASSERT_NE(0, applypatch_modes(args6.size(), args6.data()));
}
-TEST_F(ApplyPatchFullTest, ApplyCorruptedInNewLocation) {
- mangle_file(old_file);
- std::vector<char*> sha1s;
- sha1s.push_back(&bad_sha1_a[0]);
- sha1s.push_back(&old_sha1[0]);
- int ap_result = applypatch(&old_file[0],
- &output_loc[0],
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_EQ(0, ap_result);
- ASSERT_TRUE(file_cmp(output_loc, new_file));
- ap_result = applypatch(&old_file[0],
- &output_loc[0],
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_EQ(0, ap_result);
- ASSERT_TRUE(file_cmp(output_loc, new_file));
+TEST(ApplyPatchModesTest, CheckModeInvalidArgs) {
+ // Insufficient args.
+ ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" }));
+}
+
+TEST(ApplyPatchModesTest, SpaceModeInvalidArgs) {
+ // Insufficient args.
+ ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-s" }));
+
+ // Invalid bytes arg.
+ ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "x" }));
+
+ // 0 is invalid.
+ ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0" }));
+
+ // 0x10 is fine.
+ ASSERT_EQ(0, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0x10" }));
}
-TEST_F(ApplyPatchDoubleCacheTest, ApplyDoubleCorruptedInNewLocation) {
- mangle_file(old_file);
- mangle_file(cache_file);
-
- std::vector<char*> sha1s;
- sha1s.push_back(&bad_sha1_a[0]);
- sha1s.push_back(&old_sha1[0]);
- int ap_result = applypatch(&old_file[0],
- &output_loc[0],
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_NE(0, ap_result);
- ASSERT_FALSE(file_cmp(output_loc, new_file));
- ap_result = applypatch(&old_file[0],
- &output_loc[0],
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_NE(0, ap_result);
- ASSERT_FALSE(file_cmp(output_loc, new_file));
+TEST(ApplyPatchModesTest, ShowLicenses) {
+ ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" }));
}
diff --git a/tests/component/bootloader_message_test.cpp b/tests/component/bootloader_message_test.cpp
new file mode 100644
index 000000000..0357accfe
--- /dev/null
+++ b/tests/component/bootloader_message_test.cpp
@@ -0,0 +1,207 @@
+/*
+ * 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 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 <string>
+#include <vector>
+
+#include <android-base/strings.h>
+#include <bootloader_message/bootloader_message.h>
+#include <gtest/gtest.h>
+
+#include "common/component_test_util.h"
+
+class BootloaderMessageTest : public ::testing::Test {
+ protected:
+ BootloaderMessageTest() : has_misc(true) {}
+
+ virtual void SetUp() override {
+ has_misc = parse_misc();
+ }
+
+ virtual void TearDown() override {
+ // Clear the BCB.
+ if (has_misc) {
+ std::string err;
+ ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+ }
+ }
+
+ bool has_misc;
+};
+
+TEST_F(BootloaderMessageTest, clear_bootloader_message) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Clear the BCB.
+ std::string err;
+ ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+
+ // Verify the content.
+ bootloader_message boot;
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ // All the bytes should be cleared.
+ ASSERT_EQ(std::string(sizeof(boot), '\0'),
+ std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
+}
+
+TEST_F(BootloaderMessageTest, read_and_write_bootloader_message) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Write the BCB.
+ bootloader_message boot = {};
+ strlcpy(boot.command, "command", sizeof(boot.command));
+ strlcpy(boot.recovery, "message1\nmessage2\n", sizeof(boot.recovery));
+ strlcpy(boot.status, "status1", sizeof(boot.status));
+
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
+
+ // Read and verify.
+ bootloader_message boot_verify;
+ ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err;
+
+ ASSERT_EQ(std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)),
+ std::string(reinterpret_cast<const char*>(&boot_verify), sizeof(boot_verify)));
+}
+
+TEST_F(BootloaderMessageTest, write_bootloader_message_options) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Write the options to BCB.
+ std::vector<std::string> options = { "option1", "option2" };
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
+
+ // Inject some bytes into boot, which should be overwritten while reading.
+ bootloader_message boot;
+ strlcpy(boot.recovery, "random message", sizeof(boot.recovery));
+ strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved));
+
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ // Verify that command and recovery fields should be set.
+ ASSERT_EQ("boot-recovery", std::string(boot.command));
+ std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
+ ASSERT_EQ(expected, std::string(boot.recovery));
+
+ // The rest should be cleared.
+ ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
+ ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
+ ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
+ std::string(boot.reserved, sizeof(boot.reserved)));
+}
+
+TEST_F(BootloaderMessageTest, write_bootloader_message_options_empty) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Write empty vector.
+ std::vector<std::string> options;
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
+
+ // Read and verify.
+ bootloader_message boot;
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ // command and recovery fields should be set.
+ ASSERT_EQ("boot-recovery", std::string(boot.command));
+ ASSERT_EQ("recovery\n", std::string(boot.recovery));
+
+ // The rest should be cleared.
+ ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
+ ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
+ ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
+ std::string(boot.reserved, sizeof(boot.reserved)));
+}
+
+TEST_F(BootloaderMessageTest, write_bootloader_message_options_long) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Write super long message.
+ std::vector<std::string> options;
+ for (int i = 0; i < 100; i++) {
+ options.push_back("option: " + std::to_string(i));
+ }
+
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
+
+ // Read and verify.
+ bootloader_message boot;
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ // Make sure it's long enough.
+ std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
+ ASSERT_GE(expected.size(), sizeof(boot.recovery));
+
+ // command and recovery fields should be set.
+ ASSERT_EQ("boot-recovery", std::string(boot.command));
+ ASSERT_EQ(expected.substr(0, sizeof(boot.recovery) - 1), std::string(boot.recovery));
+ ASSERT_EQ('\0', boot.recovery[sizeof(boot.recovery) - 1]);
+
+ // The rest should be cleared.
+ ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
+ ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
+ ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
+ std::string(boot.reserved, sizeof(boot.reserved)));
+}
+
+TEST_F(BootloaderMessageTest, update_bootloader_message) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Inject some bytes into boot, which should be not overwritten later.
+ bootloader_message boot;
+ strlcpy(boot.recovery, "random message", sizeof(boot.recovery));
+ strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved));
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
+
+ // Update the BCB message.
+ std::vector<std::string> options = { "option1", "option2" };
+ ASSERT_TRUE(update_bootloader_message(options, &err)) << "Failed to update BCB: " << err;
+
+ bootloader_message boot_verify;
+ ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err;
+
+ // Verify that command and recovery fields should be set.
+ ASSERT_EQ("boot-recovery", std::string(boot_verify.command));
+ std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
+ ASSERT_EQ(expected, std::string(boot_verify.recovery));
+
+ // The rest should be intact.
+ ASSERT_EQ(std::string(boot.status), std::string(boot_verify.status));
+ ASSERT_EQ(std::string(boot.stage), std::string(boot_verify.stage));
+ ASSERT_EQ(std::string(boot.reserved), std::string(boot_verify.reserved));
+}
diff --git a/tests/component/edify_test.cpp b/tests/component/edify_test.cpp
new file mode 100644
index 000000000..61a1e6b64
--- /dev/null
+++ b/tests/component/edify_test.cpp
@@ -0,0 +1,170 @@
+/*
+ * 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 char* expr_str, const char* expected) {
+ std::unique_ptr<Expr> e;
+ int error_count = 0;
+ EXPECT_EQ(0, parse_string(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:
+ virtual 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) {
+ // big string
+ expect(std::string(8192, 's').c_str(), std::string(8192, 's').c_str());
+}
+
+TEST_F(EdifyTest, unknown_function) {
+ // unknown function
+ const char* script1 = "unknown_function()";
+ std::unique_ptr<Expr> expr;
+ int error_count = 0;
+ EXPECT_EQ(1, parse_string(script1, &expr, &error_count));
+ EXPECT_EQ(1, error_count);
+
+ const char* script2 = "abc; unknown_function()";
+ error_count = 0;
+ EXPECT_EQ(1, parse_string(script2, &expr, &error_count));
+ EXPECT_EQ(1, error_count);
+
+ const char* script3 = "unknown_function1() || yes";
+ error_count = 0;
+ EXPECT_EQ(1, parse_string(script3, &expr, &error_count));
+ EXPECT_EQ(1, error_count);
+}
diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp
new file mode 100644
index 000000000..2f648501c
--- /dev/null
+++ b/tests/component/imgdiff_test.cpp
@@ -0,0 +1,585 @@
+/*
+ * 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 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 <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/memory.h>
+#include <android-base/test_utils.h>
+#include <applypatch/imgdiff.h>
+#include <applypatch/imgpatch.h>
+#include <gtest/gtest.h>
+#include <ziparchive/zip_writer.h>
+
+using android::base::get_unaligned;
+
+static ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) {
+ std::string* s = static_cast<std::string*>(token);
+ s->append(reinterpret_cast<const char*>(data), len);
+ return len;
+}
+
+// Sanity check for the given imgdiff patch header.
+static void verify_patch_header(const std::string& patch, size_t* num_normal, size_t* num_raw,
+ size_t* num_deflate) {
+ const size_t size = patch.size();
+ const char* data = patch.data();
+
+ ASSERT_GE(size, 12U);
+ ASSERT_EQ("IMGDIFF2", std::string(data, 8));
+
+ const int num_chunks = get_unaligned<int32_t>(data + 8);
+ ASSERT_GE(num_chunks, 0);
+
+ size_t normal = 0;
+ size_t raw = 0;
+ size_t deflate = 0;
+
+ size_t pos = 12;
+ for (int i = 0; i < num_chunks; ++i) {
+ ASSERT_LE(pos + 4, size);
+ int type = get_unaligned<int32_t>(data + pos);
+ pos += 4;
+ if (type == CHUNK_NORMAL) {
+ pos += 24;
+ ASSERT_LE(pos, size);
+ normal++;
+ } else if (type == CHUNK_RAW) {
+ ASSERT_LE(pos + 4, size);
+ ssize_t data_len = get_unaligned<int32_t>(data + pos);
+ ASSERT_GT(data_len, 0);
+ pos += 4 + data_len;
+ ASSERT_LE(pos, size);
+ raw++;
+ } else if (type == CHUNK_DEFLATE) {
+ pos += 60;
+ ASSERT_LE(pos, size);
+ deflate++;
+ } else {
+ FAIL() << "Invalid patch type: " << type;
+ }
+ }
+
+ if (num_normal != nullptr) *num_normal = normal;
+ if (num_raw != nullptr) *num_raw = raw;
+ if (num_deflate != nullptr) *num_deflate = deflate;
+}
+
+TEST(ImgdiffTest, invalid_args) {
+ // Insufficient inputs.
+ ASSERT_EQ(2, imgdiff(1, (const char* []){ "imgdiff" }));
+ ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-z" }));
+ ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-b" }));
+ ASSERT_EQ(2, imgdiff(3, (const char* []){ "imgdiff", "-z", "-b" }));
+
+ // Failed to read bonus file.
+ ASSERT_EQ(1, imgdiff(3, (const char* []){ "imgdiff", "-b", "doesntexist" }));
+
+ // Failed to read input files.
+ ASSERT_EQ(1, imgdiff(4, (const char* []){ "imgdiff", "doesntexist", "doesntexist", "output" }));
+ ASSERT_EQ(
+ 1, imgdiff(5, (const char* []){ "imgdiff", "-z", "doesntexist", "doesntexist", "output" }));
+}
+
+TEST(ImgdiffTest, image_mode_smoke) {
+ // Random bytes.
+ const std::string src("abcdefg");
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ const std::string tgt("abcdefgxyz");
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect one CHUNK_RAW entry.
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(0U, num_deflate);
+ ASSERT_EQ(1U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, zip_mode_smoke_store) {
+ // Construct src and tgt zip files.
+ TemporaryFile src_file;
+ FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+ ZipWriter src_writer(src_file_ptr);
+ ASSERT_EQ(0, src_writer.StartEntry("file1.txt", 0)); // Store mode.
+ const std::string src_content("abcdefg");
+ ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
+ ASSERT_EQ(0, src_writer.FinishEntry());
+ ASSERT_EQ(0, src_writer.Finish());
+ ASSERT_EQ(0, fclose(src_file_ptr));
+
+ TemporaryFile tgt_file;
+ FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+ ZipWriter tgt_writer(tgt_file_ptr);
+ ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", 0)); // Store mode.
+ const std::string tgt_content("abcdefgxyz");
+ ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+ ASSERT_EQ(0, tgt_writer.FinishEntry());
+ ASSERT_EQ(0, tgt_writer.Finish());
+ ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+ // Compute patch.
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string tgt;
+ ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+ std::string src;
+ ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src));
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect one CHUNK_RAW entry.
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(0U, num_deflate);
+ ASSERT_EQ(1U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, zip_mode_smoke_compressed) {
+ // Construct src and tgt zip files.
+ TemporaryFile src_file;
+ FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+ ZipWriter src_writer(src_file_ptr);
+ ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+ const std::string src_content("abcdefg");
+ ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
+ ASSERT_EQ(0, src_writer.FinishEntry());
+ ASSERT_EQ(0, src_writer.Finish());
+ ASSERT_EQ(0, fclose(src_file_ptr));
+
+ TemporaryFile tgt_file;
+ FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+ ZipWriter tgt_writer(tgt_file_ptr);
+ ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+ const std::string tgt_content("abcdefgxyz");
+ ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+ ASSERT_EQ(0, tgt_writer.FinishEntry());
+ ASSERT_EQ(0, tgt_writer.Finish());
+ ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+ // Compute patch.
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string tgt;
+ ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+ std::string src;
+ ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src));
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(1U, num_deflate);
+ ASSERT_EQ(2U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
+ // Construct src and tgt zip files.
+ TemporaryFile src_file;
+ FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+ ZipWriter src_writer(src_file_ptr);
+ ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+ const std::string src_content("abcdefg");
+ ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
+ ASSERT_EQ(0, src_writer.FinishEntry());
+ ASSERT_EQ(0, src_writer.Finish());
+ ASSERT_EQ(0, fclose(src_file_ptr));
+
+ TemporaryFile tgt_file;
+ FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+ ZipWriter tgt_writer(tgt_file_ptr);
+ ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+ const std::string tgt_content("abcdefgxyz");
+ ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+ ASSERT_EQ(0, tgt_writer.FinishEntry());
+ ASSERT_EQ(0, tgt_writer.Finish());
+ // Add trailing zeros to the target zip file.
+ std::vector<uint8_t> zeros(10);
+ ASSERT_EQ(zeros.size(), fwrite(zeros.data(), sizeof(uint8_t), zeros.size(), tgt_file_ptr));
+ ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+ // Compute patch.
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string tgt;
+ ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+ std::string src;
+ ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src));
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(1U, num_deflate);
+ ASSERT_EQ(2U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_simple) {
+ // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd).
+ const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+ '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac',
+ '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03',
+ '\x00', '\x00', '\x00' };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: "abcdefgxyz" + gzipped "xxyyzz".
+ const std::vector<char> tgt_data = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z', '\x1f', '\x8b',
+ '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac',
+ '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00'
+ };
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(1U, num_deflate);
+ ASSERT_EQ(2U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_different_num_chunks) {
+ // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd) + gzipped "test".
+ const std::vector<char> src_data = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '\x1f', '\x8b', '\x08',
+ '\x00', '\xc4', '\x1e', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac', '\x02',
+ '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03', '\x00', '\x00', '\x00', '\x1f', '\x8b',
+ '\x08', '\x00', '\xb2', '\x3a', '\x53', '\x58', '\x00', '\x03', '\x2b', '\x49', '\x2d',
+ '\x2e', '\x01', '\x00', '\x0c', '\x7e', '\x7f', '\xd8', '\x04', '\x00', '\x00', '\x00'
+ };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: "abcdefgxyz" + gzipped "xxyyzz".
+ const std::vector<char> tgt_data = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z', '\x1f', '\x8b',
+ '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac',
+ '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00'
+ };
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(1, imgdiff(args.size(), args.data()));
+}
+
+TEST(ImgdiffTest, image_mode_merge_chunks) {
+ // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd).
+ const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+ '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac',
+ '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03',
+ '\x00', '\x00', '\x00' };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: gzipped "xyz" + "abcdefgh".
+ const std::vector<char> tgt_data = {
+ '\x1f', '\x8b', '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8',
+ '\xa8', '\xac', '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00',
+ '\x00', '\x00', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z'
+ };
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ // Since a gzipped entry will become CHUNK_RAW (header) + CHUNK_DEFLATE (data) +
+ // CHUNK_RAW (footer), they both should contain the same chunk types after merging.
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(1U, num_deflate);
+ ASSERT_EQ(2U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_spurious_magic) {
+ // src: "abcdefgh" + '0x1f8b0b00' + some bytes.
+ const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+ '\x53', '\x58', 't', 'e', 's', 't' };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: "abcdefgxyz".
+ const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect one CHUNK_RAW (header) entry.
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(0U, num_deflate);
+ ASSERT_EQ(1U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_short_input1) {
+ // src: "abcdefgh" + '0x1f8b0b'.
+ const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', '\x1f', '\x8b', '\x08' };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: "abcdefgxyz".
+ const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect one CHUNK_RAW (header) entry.
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(0U, num_deflate);
+ ASSERT_EQ(1U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_short_input2) {
+ // src: "abcdefgh" + '0x1f8b0b00'.
+ const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', '\x1f', '\x8b', '\x08', '\x00' };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: "abcdefgxyz".
+ const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect one CHUNK_RAW (header) entry.
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(0U, num_deflate);
+ ASSERT_EQ(1U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_single_entry_long) {
+ // src: "abcdefgh" + '0x1f8b0b00' + some bytes.
+ const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+ '\x53', '\x58', 't', 'e', 's', 't' };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: "abcdefgxyz" + 200 bytes.
+ std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+ tgt_data.resize(tgt_data.size() + 200);
+
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect one CHUNK_NORMAL entry, since it's exceeding the 160-byte limit for RAW.
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(1U, num_normal);
+ ASSERT_EQ(0U, num_deflate);
+ ASSERT_EQ(0U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp
new file mode 100644
index 000000000..a5c0c1025
--- /dev/null
+++ b/tests/component/install_test.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2017 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 <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <vintf/VintfObjectRecovery.h>
+#include <ziparchive/zip_archive.h>
+#include <ziparchive/zip_writer.h>
+
+#include "install.h"
+#include "private/install.h"
+
+TEST(InstallTest, verify_package_compatibility_no_entry) {
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.fd, "w");
+ ZipWriter writer(zip_file);
+ // The archive must have something to be opened correctly.
+ ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+
+ // Doesn't contain compatibility zip entry.
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ ASSERT_TRUE(verify_package_compatibility(zip));
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, verify_package_compatibility_invalid_entry) {
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.fd, "w");
+ ZipWriter writer(zip_file);
+ ASSERT_EQ(0, writer.StartEntry("compatibility.zip", 0));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+
+ // Empty compatibility zip entry.
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ ASSERT_FALSE(verify_package_compatibility(zip));
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
+ TemporaryFile compatibility_zip_file;
+ FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+ ZipWriter compatibility_zip_writer(compatibility_zip);
+ ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
+ std::string malformed_xml = "malformed";
+ ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(malformed_xml.data(), malformed_xml.size()));
+ ASSERT_EQ(0, compatibility_zip_writer.FinishEntry());
+ ASSERT_EQ(0, compatibility_zip_writer.Finish());
+ ASSERT_EQ(0, fclose(compatibility_zip));
+
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.fd, "w");
+ ZipWriter writer(zip_file);
+ ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
+ std::string compatibility_zip_content;
+ ASSERT_TRUE(
+ android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
+ ASSERT_EQ(0,
+ writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ std::vector<std::string> compatibility_info;
+ compatibility_info.push_back(malformed_xml);
+ // Malformed compatibility zip is expected to be rejected by libvintf. But we defer that to
+ // libvintf.
+ std::string err;
+ bool result =
+ android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0;
+ ASSERT_EQ(result, verify_package_compatibility(zip));
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml) {
+ static constexpr const char* system_manifest_xml_path = "/system/manifest.xml";
+ if (access(system_manifest_xml_path, R_OK) == -1) {
+ GTEST_LOG_(INFO) << "Test skipped on devices w/o /system/manifest.xml.";
+ return;
+ }
+ std::string system_manifest_xml_content;
+ ASSERT_TRUE(
+ android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content));
+ TemporaryFile compatibility_zip_file;
+ FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+ ZipWriter compatibility_zip_writer(compatibility_zip);
+ ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
+ ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(system_manifest_xml_content.data(),
+ system_manifest_xml_content.size()));
+ ASSERT_EQ(0, compatibility_zip_writer.FinishEntry());
+ ASSERT_EQ(0, compatibility_zip_writer.Finish());
+ ASSERT_EQ(0, fclose(compatibility_zip));
+
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.fd, "w");
+ ZipWriter writer(zip_file);
+ ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
+ std::string compatibility_zip_content;
+ ASSERT_TRUE(
+ android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
+ ASSERT_EQ(0,
+ writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ std::vector<std::string> compatibility_info;
+ compatibility_info.push_back(system_manifest_xml_content);
+ std::string err;
+ bool result =
+ android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0;
+ // Make sure the result is consistent with libvintf library.
+ ASSERT_EQ(result, verify_package_compatibility(zip));
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, update_binary_command_smoke) {
+#ifdef AB_OTA_UPDATER
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.fd, "w");
+ ZipWriter writer(zip_file);
+ ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.StartEntry("payload_properties.txt", kCompressStored));
+ const std::string properties = "some_properties";
+ ASSERT_EQ(0, writer.WriteBytes(properties.data(), properties.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ // A metadata entry is mandatory.
+ ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+ std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
+ ASSERT_NE("", timestamp);
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp,
+ },
+ "\n");
+ ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ int status_fd = 10;
+ std::string path = "/path/to/update.zip";
+ std::vector<std::string> cmd;
+ ASSERT_EQ(0, update_binary_command(path, zip, 0, status_fd, &cmd));
+ ASSERT_EQ("/sbin/update_engine_sideload", cmd[0]);
+ ASSERT_EQ("--payload=file://" + path, cmd[1]);
+ ASSERT_EQ("--headers=" + properties, cmd[3]);
+ ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
+ CloseArchive(zip);
+#else
+ // Cannot test update_binary_command() because it tries to extract update-binary to /tmp.
+ GTEST_LOG_(INFO) << "Test skipped on non-A/B device.";
+#endif // AB_OTA_UPDATER
+}
+
+TEST(InstallTest, update_binary_command_invalid) {
+#ifdef AB_OTA_UPDATER
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.fd, "w");
+ ZipWriter writer(zip_file);
+ // Missing payload_properties.txt.
+ ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
+ ASSERT_EQ(0, writer.FinishEntry());
+ // A metadata entry is mandatory.
+ ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+ std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
+ ASSERT_NE("", timestamp);
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp,
+ },
+ "\n");
+ ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ int status_fd = 10;
+ std::string path = "/path/to/update.zip";
+ std::vector<std::string> cmd;
+ ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(path, zip, 0, status_fd, &cmd));
+ CloseArchive(zip);
+#else
+ // Cannot test update_binary_command() because it tries to extract update-binary to /tmp.
+ GTEST_LOG_(INFO) << "Test skipped on non-A/B device.";
+#endif // AB_OTA_UPDATER
+}
diff --git a/tests/component/sideload_test.cpp b/tests/component/sideload_test.cpp
new file mode 100644
index 000000000..ea93e9b84
--- /dev/null
+++ b/tests/component/sideload_test.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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 <unistd.h>
+#include <gtest/gtest.h>
+
+TEST(SideloadTest, fusedevice) {
+ ASSERT_NE(-1, access("/dev/fuse", R_OK | W_OK));
+}
diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp
new file mode 100644
index 000000000..4f2b8164f
--- /dev/null
+++ b/tests/component/uncrypt_test.cpp
@@ -0,0 +1,192 @@
+/*
+ * 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 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 <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <bootloader_message/bootloader_message.h>
+#include <gtest/gtest.h>
+
+#include "common/component_test_util.h"
+
+static const std::string UNCRYPT_SOCKET = "/dev/socket/uncrypt";
+static const std::string INIT_SVC_SETUP_BCB = "init.svc.setup-bcb";
+static const std::string INIT_SVC_CLEAR_BCB = "init.svc.clear-bcb";
+static const std::string INIT_SVC_UNCRYPT = "init.svc.uncrypt";
+static constexpr int SOCKET_CONNECTION_MAX_RETRY = 30;
+
+class UncryptTest : public ::testing::Test {
+ protected:
+ UncryptTest() : has_misc(true) {}
+
+ virtual void SetUp() override {
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", "uncrypt"));
+
+ bool success = false;
+ for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+ std::string setup_bcb = android::base::GetProperty(INIT_SVC_SETUP_BCB, "");
+ std::string clear_bcb = android::base::GetProperty(INIT_SVC_CLEAR_BCB, "");
+ std::string uncrypt = android::base::GetProperty(INIT_SVC_UNCRYPT, "");
+ LOG(INFO) << "setup-bcb: [" << setup_bcb << "] clear-bcb: [" << clear_bcb << "] uncrypt: ["
+ << uncrypt << "]";
+ if (setup_bcb != "running" && clear_bcb != "running" && uncrypt != "running") {
+ success = true;
+ break;
+ }
+ sleep(1);
+ }
+
+ ASSERT_TRUE(success) << "uncrypt service is not available.";
+
+ has_misc = parse_misc();
+ }
+
+ bool has_misc;
+};
+
+TEST_F(UncryptTest, setup_bcb) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Trigger the setup-bcb service.
+ ASSERT_TRUE(android::base::SetProperty("ctl.start", "setup-bcb"));
+
+ // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
+ sleep(1);
+
+ struct sockaddr_un un = {};
+ un.sun_family = AF_UNIX;
+ strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path));
+
+ int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ ASSERT_NE(-1, sockfd);
+
+ // Connect to the uncrypt socket.
+ bool success = false;
+ for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+ if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) {
+ success = true;
+ break;
+ }
+ sleep(1);
+ }
+ ASSERT_TRUE(success);
+
+ // Send out the BCB message.
+ std::string message = "--update_message=abc value";
+ std::string message_in_bcb = "recovery\n--update_message=abc value\n";
+ int length = static_cast<int>(message.size());
+ int length_out = htonl(length);
+ ASSERT_TRUE(android::base::WriteFully(sockfd, &length_out, sizeof(int)))
+ << "Failed to write length: " << strerror(errno);
+ ASSERT_TRUE(android::base::WriteFully(sockfd, message.data(), length))
+ << "Failed to write message: " << strerror(errno);
+
+ // Check the status code from uncrypt.
+ int status;
+ ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int)));
+ ASSERT_EQ(100U, ntohl(status));
+
+ // Ack having received the status code.
+ int code = 0;
+ ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
+
+ ASSERT_EQ(0, close(sockfd));
+
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
+
+ // Verify the message by reading from BCB directly.
+ bootloader_message boot;
+ std::string err;
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ ASSERT_EQ("boot-recovery", std::string(boot.command));
+ ASSERT_EQ(message_in_bcb, std::string(boot.recovery));
+
+ // The rest of the boot.recovery message should be zero'd out.
+ ASSERT_LE(message_in_bcb.size(), sizeof(boot.recovery));
+ size_t left = sizeof(boot.recovery) - message_in_bcb.size();
+ ASSERT_EQ(std::string(left, '\0'), std::string(&boot.recovery[message_in_bcb.size()], left));
+
+ // Clear the BCB.
+ ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+}
+
+TEST_F(UncryptTest, clear_bcb) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Trigger the clear-bcb service.
+ ASSERT_TRUE(android::base::SetProperty("ctl.start", "clear-bcb"));
+
+ // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
+ sleep(1);
+
+ struct sockaddr_un un = {};
+ un.sun_family = AF_UNIX;
+ strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path));
+
+ int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ ASSERT_NE(-1, sockfd);
+
+ // Connect to the uncrypt socket.
+ bool success = false;
+ for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+ if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) {
+ success = true;
+ break;
+ }
+ sleep(1);
+ }
+ ASSERT_TRUE(success);
+
+ // Check the status code from uncrypt.
+ int status;
+ ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int)));
+ ASSERT_EQ(100U, ntohl(status));
+
+ // Ack having received the status code.
+ int code = 0;
+ ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
+
+ ASSERT_EQ(0, close(sockfd));
+
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
+
+ // Verify the content by reading from BCB directly.
+ bootloader_message boot;
+ std::string err;
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ // All the bytes should be cleared.
+ ASSERT_EQ(std::string(sizeof(boot), '\0'),
+ std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
+}
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
new file mode 100644
index 000000000..5652ddf46
--- /dev/null
+++ b/tests/component/updater_test.cpp
@@ -0,0 +1,609 @@
+/*
+ * 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 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 <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+#include <bootloader_message/bootloader_message.h>
+#include <bsdiff.h>
+#include <gtest/gtest.h>
+#include <ziparchive/zip_archive.h>
+#include <ziparchive/zip_writer.h>
+
+#include "common/test_constants.h"
+#include "edify/expr.h"
+#include "error_code.h"
+#include "otautil/SysUtil.h"
+#include "print_sha1.h"
+#include "updater/blockimg.h"
+#include "updater/install.h"
+#include "updater/updater.h"
+
+struct selabel_handle *sehandle = nullptr;
+
+static void expect(const char* expected, const char* expr_str, CauseCode cause_code,
+ UpdaterInfo* info = nullptr) {
+ std::unique_ptr<Expr> e;
+ int error_count = 0;
+ ASSERT_EQ(0, parse_string(expr_str, &e, &error_count));
+ ASSERT_EQ(0, error_count);
+
+ State state(expr_str, info);
+
+ std::string result;
+ bool status = Evaluate(&state, e, &result);
+
+ if (expected == nullptr) {
+ ASSERT_FALSE(status);
+ } else {
+ ASSERT_TRUE(status);
+ ASSERT_STREQ(expected, result.c_str());
+ }
+
+ // Error code is set in updater/updater.cpp only, by parsing State.errmsg.
+ ASSERT_EQ(kNoError, state.error_code);
+
+ // Cause code should always be available.
+ ASSERT_EQ(cause_code, state.cause_code);
+}
+
+static std::string get_sha1(const std::string& content) {
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(reinterpret_cast<const uint8_t*>(content.c_str()), content.size(), digest);
+ return print_sha1(digest);
+}
+
+class UpdaterTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() override {
+ RegisterBuiltins();
+ RegisterInstallFunctions();
+ RegisterBlockImageFunctions();
+ }
+};
+
+TEST_F(UpdaterTest, getprop) {
+ expect(android::base::GetProperty("ro.product.device", "").c_str(),
+ "getprop(\"ro.product.device\")",
+ kNoCause);
+
+ expect(android::base::GetProperty("ro.build.fingerprint", "").c_str(),
+ "getprop(\"ro.build.fingerprint\")",
+ kNoCause);
+
+ // getprop() accepts only one parameter.
+ expect(nullptr, "getprop()", kArgsParsingFailure);
+ expect(nullptr, "getprop(\"arg1\", \"arg2\")", kArgsParsingFailure);
+}
+
+TEST_F(UpdaterTest, sha1_check) {
+ // sha1_check(data) returns the SHA-1 of the data.
+ expect("81fe8bfe87576c3ecb22426f8e57847382917acf", "sha1_check(\"abcd\")", kNoCause);
+ expect("da39a3ee5e6b4b0d3255bfef95601890afd80709", "sha1_check(\"\")", kNoCause);
+
+ // sha1_check(data, sha1_hex, [sha1_hex, ...]) returns the matched SHA-1.
+ expect("81fe8bfe87576c3ecb22426f8e57847382917acf",
+ "sha1_check(\"abcd\", \"81fe8bfe87576c3ecb22426f8e57847382917acf\")",
+ kNoCause);
+
+ expect("81fe8bfe87576c3ecb22426f8e57847382917acf",
+ "sha1_check(\"abcd\", \"wrong_sha1\", \"81fe8bfe87576c3ecb22426f8e57847382917acf\")",
+ kNoCause);
+
+ // Or "" if there's no match.
+ expect("",
+ "sha1_check(\"abcd\", \"wrong_sha1\")",
+ kNoCause);
+
+ expect("",
+ "sha1_check(\"abcd\", \"wrong_sha1\", \"wrong_sha2\")",
+ kNoCause);
+
+ // sha1_check() expects at least one argument.
+ expect(nullptr, "sha1_check()", kArgsParsingFailure);
+}
+
+TEST_F(UpdaterTest, apply_patch_check) {
+ // Zero-argument is not valid.
+ expect(nullptr, "apply_patch_check()", kArgsParsingFailure);
+
+ // File not found.
+ expect("", "apply_patch_check(\"/doesntexist\")", kNoCause);
+
+ std::string src_file = from_testdata_base("old.file");
+ std::string src_content;
+ ASSERT_TRUE(android::base::ReadFileToString(src_file, &src_content));
+ size_t src_size = src_content.size();
+ std::string src_hash = get_sha1(src_content);
+
+ // One-argument with EMMC:file:size:sha1 should pass the check.
+ std::string filename = android::base::Join(
+ std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size), src_hash }, ":");
+ std::string cmd = "apply_patch_check(\"" + filename + "\")";
+ expect("t", cmd.c_str(), kNoCause);
+
+ // EMMC:file:(size-1):sha1:(size+1):sha1 should fail the check.
+ std::string filename_bad = android::base::Join(
+ std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size - 1), src_hash,
+ std::to_string(src_size + 1), src_hash },
+ ":");
+ cmd = "apply_patch_check(\"" + filename_bad + "\")";
+ expect("", cmd.c_str(), kNoCause);
+
+ // EMMC:file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check.
+ filename_bad =
+ android::base::Join(std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size - 1),
+ src_hash, std::to_string(src_size), src_hash,
+ std::to_string(src_size + 1), src_hash },
+ ":");
+ cmd = "apply_patch_check(\"" + filename_bad + "\")";
+ expect("t", cmd.c_str(), kNoCause);
+
+ // Multiple arguments.
+ cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"wrong_sha2\")";
+ expect("", cmd.c_str(), kNoCause);
+
+ cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"" + src_hash +
+ "\", \"wrong_sha2\")";
+ expect("t", cmd.c_str(), kNoCause);
+
+ cmd = "apply_patch_check(\"" + filename_bad + "\", \"wrong_sha1\", \"" + src_hash +
+ "\", \"wrong_sha2\")";
+ expect("t", cmd.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, file_getprop) {
+ // file_getprop() expects two arguments.
+ expect(nullptr, "file_getprop()", kArgsParsingFailure);
+ expect(nullptr, "file_getprop(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "file_getprop(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ // File doesn't exist.
+ expect(nullptr, "file_getprop(\"/doesntexist\", \"key1\")", kFileGetPropFailure);
+
+ // Reject too large files (current limit = 65536).
+ TemporaryFile temp_file1;
+ std::string buffer(65540, '\0');
+ ASSERT_TRUE(android::base::WriteStringToFile(buffer, temp_file1.path));
+
+ // Read some keys.
+ TemporaryFile temp_file2;
+ std::string content("ro.product.name=tardis\n"
+ "# comment\n\n\n"
+ "ro.product.model\n"
+ "ro.product.board = magic \n");
+ ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file2.path));
+
+ std::string script1("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.name\")");
+ expect("tardis", script1.c_str(), kNoCause);
+
+ std::string script2("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.board\")");
+ expect("magic", script2.c_str(), kNoCause);
+
+ // No match.
+ std::string script3("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.wrong\")");
+ expect("", script3.c_str(), kNoCause);
+
+ std::string script4("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.name=\")");
+ expect("", script4.c_str(), kNoCause);
+
+ std::string script5("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.nam\")");
+ expect("", script5.c_str(), kNoCause);
+
+ std::string script6("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.model\")");
+ expect("", script6.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, package_extract_dir) {
+ // package_extract_dir expects 2 arguments.
+ expect(nullptr, "package_extract_dir()", kArgsParsingFailure);
+ expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Need to set up the ziphandle.
+ UpdaterInfo updater_info;
+ updater_info.package_zip = handle;
+
+ // Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", "<dir>").
+ TemporaryDir td;
+ std::string temp_dir(td.path);
+ std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")");
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ // Verify.
+ std::string data;
+ std::string file_c = temp_dir + "/c.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+ ASSERT_EQ(kCTxtContents, data);
+
+ std::string file_d = temp_dir + "/d.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+ ASSERT_EQ(kDTxtContents, data);
+
+ // Modify the contents in order to retry. It's expected to be overwritten.
+ ASSERT_TRUE(android::base::WriteStringToFile("random", file_c));
+ ASSERT_TRUE(android::base::WriteStringToFile("random", file_d));
+
+ // Extract again and verify.
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+ ASSERT_EQ(kCTxtContents, data);
+ ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+ ASSERT_EQ(kDTxtContents, data);
+
+ // Clean up the temp files under td.
+ ASSERT_EQ(0, unlink(file_c.c_str()));
+ ASSERT_EQ(0, unlink(file_d.c_str()));
+
+ // Extracting "b/" (with slash) should give the same result.
+ script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")";
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+ ASSERT_EQ(kCTxtContents, data);
+ ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+ ASSERT_EQ(kDTxtContents, data);
+
+ ASSERT_EQ(0, unlink(file_c.c_str()));
+ ASSERT_EQ(0, unlink(file_d.c_str()));
+
+ // Extracting "" is allowed. The entries will carry the path name.
+ script = "package_extract_dir(\"\", \"" + temp_dir + "\")";
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ std::string file_a = temp_dir + "/a.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_a, &data));
+ ASSERT_EQ(kATxtContents, data);
+ std::string file_b = temp_dir + "/b.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_b, &data));
+ ASSERT_EQ(kBTxtContents, data);
+ std::string file_b_c = temp_dir + "/b/c.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data));
+ ASSERT_EQ(kCTxtContents, data);
+ std::string file_b_d = temp_dir + "/b/d.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data));
+ ASSERT_EQ(kDTxtContents, data);
+
+ ASSERT_EQ(0, unlink(file_a.c_str()));
+ ASSERT_EQ(0, unlink(file_b.c_str()));
+ ASSERT_EQ(0, unlink(file_b_c.c_str()));
+ ASSERT_EQ(0, unlink(file_b_d.c_str()));
+ ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str()));
+
+ // Extracting non-existent entry should still give "t".
+ script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")";
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ // Only relative zip_path is allowed.
+ script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")";
+ expect("", script.c_str(), kNoCause, &updater_info);
+
+ // Only absolute dest_path is allowed.
+ script = "package_extract_dir(\"b\", \"path\")";
+ expect("", script.c_str(), kNoCause, &updater_info);
+
+ CloseArchive(handle);
+}
+
+// TODO: Test extracting to block device.
+TEST_F(UpdaterTest, package_extract_file) {
+ // package_extract_file expects 1 or 2 arguments.
+ expect(nullptr, "package_extract_file()", kArgsParsingFailure);
+ expect(nullptr, "package_extract_file(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Need to set up the ziphandle.
+ UpdaterInfo updater_info;
+ updater_info.package_zip = handle;
+
+ // Two-argument version.
+ TemporaryFile temp_file1;
+ std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")");
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ // Verify the extracted entry.
+ std::string data;
+ ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data));
+ ASSERT_EQ(kATxtContents, data);
+
+ // Now extract another entry to the same location, which should overwrite.
+ script = "package_extract_file(\"b.txt\", \"" + std::string(temp_file1.path) + "\")";
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data));
+ ASSERT_EQ(kBTxtContents, data);
+
+ // Missing zip entry. The two-argument version doesn't abort.
+ script = "package_extract_file(\"doesntexist\", \"" + std::string(temp_file1.path) + "\")";
+ expect("", script.c_str(), kNoCause, &updater_info);
+
+ // Extract to /dev/full should fail.
+ script = "package_extract_file(\"a.txt\", \"/dev/full\")";
+ expect("", script.c_str(), kNoCause, &updater_info);
+
+ // One-argument version.
+ script = "sha1_check(package_extract_file(\"a.txt\"))";
+ expect(kATxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info);
+
+ script = "sha1_check(package_extract_file(\"b.txt\"))";
+ expect(kBTxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info);
+
+ // Missing entry. The one-argument version aborts the evaluation.
+ script = "package_extract_file(\"doesntexist\")";
+ expect(nullptr, script.c_str(), kPackageExtractFileFailure, &updater_info);
+
+ CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, write_value) {
+ // write_value() expects two arguments.
+ expect(nullptr, "write_value()", kArgsParsingFailure);
+ expect(nullptr, "write_value(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "write_value(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ // filename cannot be empty.
+ expect(nullptr, "write_value(\"value\", \"\")", kArgsParsingFailure);
+
+ // Write some value to file.
+ TemporaryFile temp_file;
+ std::string value = "magicvalue";
+ std::string script("write_value(\"" + value + "\", \"" + std::string(temp_file.path) + "\")");
+ expect("t", script.c_str(), kNoCause);
+
+ // Verify the content.
+ std::string content;
+ ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content));
+ ASSERT_EQ(value, content);
+
+ // Allow writing empty string.
+ script = "write_value(\"\", \"" + std::string(temp_file.path) + "\")";
+ expect("t", script.c_str(), kNoCause);
+
+ // Verify the content.
+ ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content));
+ ASSERT_EQ("", content);
+
+ // It should fail gracefully when write fails.
+ script = "write_value(\"value\", \"/proc/0/file1\")";
+ expect("", script.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, get_stage) {
+ // get_stage() expects one argument.
+ expect(nullptr, "get_stage()", kArgsParsingFailure);
+ expect(nullptr, "get_stage(\"arg1\", \"arg2\")", kArgsParsingFailure);
+ expect(nullptr, "get_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ // Set up a local file as BCB.
+ TemporaryFile tf;
+ std::string temp_file(tf.path);
+ bootloader_message boot;
+ strlcpy(boot.stage, "2/3", sizeof(boot.stage));
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err));
+
+ // Can read the stage value.
+ std::string script("get_stage(\"" + temp_file + "\")");
+ expect("2/3", script.c_str(), kNoCause);
+
+ // Bad BCB path.
+ script = "get_stage(\"doesntexist\")";
+ expect("", script.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, set_stage) {
+ // set_stage() expects two arguments.
+ expect(nullptr, "set_stage()", kArgsParsingFailure);
+ expect(nullptr, "set_stage(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "set_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ // Set up a local file as BCB.
+ TemporaryFile tf;
+ std::string temp_file(tf.path);
+ bootloader_message boot;
+ strlcpy(boot.command, "command", sizeof(boot.command));
+ strlcpy(boot.stage, "2/3", sizeof(boot.stage));
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err));
+
+ // Write with set_stage().
+ std::string script("set_stage(\"" + temp_file + "\", \"1/3\")");
+ expect(tf.path, script.c_str(), kNoCause);
+
+ // Verify.
+ bootloader_message boot_verify;
+ ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_file, &err));
+
+ // Stage should be updated, with command part untouched.
+ ASSERT_STREQ("1/3", boot_verify.stage);
+ ASSERT_STREQ(boot.command, boot_verify.command);
+
+ // Bad BCB path.
+ script = "set_stage(\"doesntexist\", \"1/3\")";
+ expect("", script.c_str(), kNoCause);
+
+ script = "set_stage(\"/dev/full\", \"1/3\")";
+ expect("", script.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, set_progress) {
+ // set_progress() expects one argument.
+ expect(nullptr, "set_progress()", kArgsParsingFailure);
+ expect(nullptr, "set_progress(\"arg1\", \"arg2\")", kArgsParsingFailure);
+
+ // Invalid progress argument.
+ expect(nullptr, "set_progress(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "set_progress(\"3x+5\")", kArgsParsingFailure);
+ expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure);
+
+ TemporaryFile tf;
+ UpdaterInfo updater_info;
+ updater_info.cmd_pipe = fdopen(tf.fd, "w");
+ expect(".52", "set_progress(\".52\")", kNoCause, &updater_info);
+ fflush(updater_info.cmd_pipe);
+
+ std::string cmd;
+ ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
+ ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd);
+ // recovery-updater protocol expects 2 tokens ("set_progress <frac>").
+ ASSERT_EQ(2U, android::base::Split(cmd, " ").size());
+}
+
+TEST_F(UpdaterTest, show_progress) {
+ // show_progress() expects two arguments.
+ expect(nullptr, "show_progress()", kArgsParsingFailure);
+ expect(nullptr, "show_progress(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "show_progress(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ // Invalid progress arguments.
+ expect(nullptr, "show_progress(\"arg1\", \"arg2\")", kArgsParsingFailure);
+ expect(nullptr, "show_progress(\"3x+5\", \"10\")", kArgsParsingFailure);
+ expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure);
+
+ TemporaryFile tf;
+ UpdaterInfo updater_info;
+ updater_info.cmd_pipe = fdopen(tf.fd, "w");
+ expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info);
+ fflush(updater_info.cmd_pipe);
+
+ std::string cmd;
+ ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
+ ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd);
+ // recovery-updater protocol expects 3 tokens ("progress <frac> <secs>").
+ ASSERT_EQ(3U, android::base::Split(cmd, " ").size());
+}
+
+TEST_F(UpdaterTest, block_image_update) {
+ // Create a zip file with new_data and patch_data.
+ TemporaryFile zip_file;
+ FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
+ ZipWriter zip_writer(zip_file_ptr);
+
+ // Add a dummy new data.
+ ASSERT_EQ(0, zip_writer.StartEntry("new_data", 0));
+ ASSERT_EQ(0, zip_writer.FinishEntry());
+
+ // Generate and add the patch data.
+ std::string src_content = std::string(4096, 'a') + std::string(4096, 'c');
+ std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd');
+ 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::string patch_content;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content));
+ ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
+ ASSERT_EQ(0, zip_writer.WriteBytes(patch_content.data(), patch_content.size()));
+ ASSERT_EQ(0, zip_writer.FinishEntry());
+
+ // Add two transfer lists. The first one contains a bsdiff; and we expect the update to succeed.
+ std::string src_hash = get_sha1(src_content);
+ std::string tgt_hash = get_sha1(tgt_content);
+ std::vector<std::string> transfer_list = {
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + src_hash + " 2,0,2",
+ android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,2 2 - %s:2,0,2", patch_content.size(),
+ src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()),
+ "free " + src_hash,
+ };
+ ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
+ std::string commands = android::base::Join(transfer_list, '\n');
+ ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
+ ASSERT_EQ(0, zip_writer.FinishEntry());
+
+ // Stash and free some blocks, then fail the 2nd update intentionally.
+ std::vector<std::string> fail_transfer_list = {
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + tgt_hash + " 2,0,2",
+ "free " + tgt_hash,
+ "fail",
+ };
+ ASSERT_EQ(0, zip_writer.StartEntry("fail_transfer_list", 0));
+ std::string fail_commands = android::base::Join(fail_transfer_list, '\n');
+ ASSERT_EQ(0, zip_writer.WriteBytes(fail_commands.data(), fail_commands.size()));
+ ASSERT_EQ(0, zip_writer.FinishEntry());
+ ASSERT_EQ(0, zip_writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file_ptr));
+
+ MemMapping map;
+ ASSERT_EQ(0, sysMapFile(zip_file.path, &map));
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+ // Set up the handler, command_pipe, patch offset & length.
+ UpdaterInfo updater_info;
+ updater_info.package_zip = handle;
+ TemporaryFile temp_pipe;
+ updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
+ updater_info.package_zip_addr = map.addr;
+ updater_info.package_zip_len = map.length;
+
+ // Execute the commands in the 1st transfer list.
+ TemporaryFile update_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+ std::string script = "block_image_update(\"" + std::string(update_file.path) +
+ R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))";
+ expect("t", script.c_str(), kNoCause, &updater_info);
+ // The update_file should be patched correctly.
+ std::string updated_content;
+ ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
+ ASSERT_EQ(tgt_hash, get_sha1(updated_content));
+
+ // Expect the 2nd update to fail, but expect the stashed blocks to be freed.
+ script = "block_image_update(\"" + std::string(update_file.path) +
+ R"(", package_extract_file("fail_transfer_list"), "new_data", "patch_data"))";
+ expect("", script.c_str(), kNoCause, &updater_info);
+ // Updater generates the stash name based on the input file name.
+ std::string name_digest = get_sha1(update_file.path);
+ std::string stash_base = "/cache/recovery/" + name_digest;
+ ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
+ ASSERT_EQ(-1, access((stash_base + tgt_hash).c_str(), F_OK));
+ ASSERT_EQ(0, rmdir(stash_base.c_str()));
+
+ ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+ CloseArchive(handle);
+}
diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp
index 780ff2816..4c0648714 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -22,108 +22,36 @@
#include <sys/stat.h>
#include <sys/types.h>
-#include <memory>
#include <string>
#include <vector>
-#include <openssl/sha.h>
-
+#include <android-base/file.h>
#include <android-base/stringprintf.h>
+#include <android-base/test_utils.h>
-#include "common.h"
#include "common/test_constants.h"
-#include "minzip/SysUtil.h"
-#include "ui.h"
+#include "otautil/SysUtil.h"
#include "verifier.h"
-static const char* DATA_PATH = getenv("ANDROID_DATA");
-static const char* TESTDATA_PATH = "/recovery/testdata/";
-
-RecoveryUI* ui = NULL;
-
-class MockUI : public RecoveryUI {
- void Init() { }
- void SetStage(int, int) { }
- void SetLocale(const char*) { }
- void SetBackground(Icon icon) { }
- void SetSystemUpdateText(bool security_update) { }
-
- void SetProgressType(ProgressType determinate) { }
- void ShowProgress(float portion, float seconds) { }
- void SetProgress(float fraction) { }
-
- void ShowText(bool visible) { }
- bool IsTextVisible() { return false; }
- bool WasTextEverVisible() { return false; }
- void Print(const char* fmt, ...) {
- va_list ap;
- va_start(ap, fmt);
- vfprintf(stderr, fmt, ap);
- va_end(ap);
- }
- void PrintOnScreenOnly(const char* fmt, ...) {
- va_list ap;
- va_start(ap, fmt);
- vfprintf(stderr, fmt, ap);
- va_end(ap);
- }
- void ShowFile(const char*) { }
-
- void StartMenu(const char* const * headers, const char* const * items,
- int initial_selection) { }
- int SelectMenu(int sel) { return 0; }
- void EndMenu() { }
-};
-
-void
-ui_print(const char* format, ...) {
- va_list ap;
- va_start(ap, format);
- vfprintf(stdout, format, ap);
- va_end(ap);
-}
+using namespace std::string_literals;
class VerifierTest : public testing::TestWithParam<std::vector<std::string>> {
- public:
- MemMapping memmap;
- std::vector<Certificate> certs;
-
- virtual void SetUp() {
- std::vector<std::string> args = GetParam();
- std::string package =
- android::base::StringPrintf("%s%s%s%s", DATA_PATH, NATIVE_TEST_PATH,
- TESTDATA_PATH, args[0].c_str());
- if (sysMapFile(package.c_str(), &memmap) != 0) {
- FAIL() << "Failed to mmap " << package << ": " << strerror(errno)
- << "\n";
- }
-
- for (auto it = ++(args.cbegin()); it != args.cend(); ++it) {
- if (it->substr(it->length() - 3, it->length()) == "256") {
- if (certs.empty()) {
- FAIL() << "May only specify -sha256 after key type\n";
- }
- certs.back().hash_len = SHA256_DIGEST_LENGTH;
- } else {
- std::string public_key_file = android::base::StringPrintf(
- "%s%s%stest_key_%s.txt", DATA_PATH, NATIVE_TEST_PATH,
- TESTDATA_PATH, it->c_str());
- ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
- certs.back().hash_len = SHA_DIGEST_LENGTH;
- }
- }
- if (certs.empty()) {
- std::string public_key_file = android::base::StringPrintf(
- "%s%s%stest_key_e3.txt", DATA_PATH, NATIVE_TEST_PATH,
- TESTDATA_PATH);
- ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
- certs.back().hash_len = SHA_DIGEST_LENGTH;
- }
+ protected:
+ void SetUp() override {
+ std::vector<std::string> args = GetParam();
+ std::string package = from_testdata_base(args[0]);
+ if (sysMapFile(package.c_str(), &memmap) != 0) {
+ FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n";
}
- static void SetUpTestCase() {
- ui = new MockUI();
+ for (auto it = ++args.cbegin(); it != args.cend(); ++it) {
+ std::string public_key_file = from_testdata_base("testkey_" + *it + ".txt");
+ ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
}
+ }
+
+ MemMapping memmap;
+ std::vector<Certificate> certs;
};
class VerifierSuccessTest : public VerifierTest {
@@ -132,47 +60,120 @@ class VerifierSuccessTest : public VerifierTest {
class VerifierFailureTest : public VerifierTest {
};
+TEST(VerifierTest, load_keys_multiple_keys) {
+ std::string testkey_v4;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4));
+
+ std::string testkey_v3;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
+
+ std::string keys = testkey_v4 + "," + testkey_v3 + "," + testkey_v4;
+ TemporaryFile key_file1;
+ ASSERT_TRUE(android::base::WriteStringToFile(keys, key_file1.path));
+ std::vector<Certificate> certs;
+ ASSERT_TRUE(load_keys(key_file1.path, certs));
+ ASSERT_EQ(3U, certs.size());
+}
+
+TEST(VerifierTest, load_keys_invalid_keys) {
+ std::vector<Certificate> certs;
+ ASSERT_FALSE(load_keys("/doesntexist", certs));
+
+ // Empty file.
+ TemporaryFile key_file1;
+ ASSERT_FALSE(load_keys(key_file1.path, certs));
+
+ // Invalid contents.
+ ASSERT_TRUE(android::base::WriteStringToFile("invalid", key_file1.path));
+ ASSERT_FALSE(load_keys(key_file1.path, certs));
+
+ std::string testkey_v4;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4));
+
+ // Invalid key version: "v4 ..." => "v6 ...".
+ std::string invalid_key2(testkey_v4);
+ invalid_key2[1] = '6';
+ TemporaryFile key_file2;
+ ASSERT_TRUE(android::base::WriteStringToFile(invalid_key2, key_file2.path));
+ ASSERT_FALSE(load_keys(key_file2.path, certs));
+
+ // Invalid key content: inserted extra bytes ",2209831334".
+ std::string invalid_key3(testkey_v4);
+ invalid_key3.insert(invalid_key2.size() - 2, ",2209831334");
+ TemporaryFile key_file3;
+ ASSERT_TRUE(android::base::WriteStringToFile(invalid_key3, key_file3.path));
+ ASSERT_FALSE(load_keys(key_file3.path, certs));
+
+ // Invalid key: the last key must not end with an extra ','.
+ std::string invalid_key4 = testkey_v4 + ",";
+ TemporaryFile key_file4;
+ ASSERT_TRUE(android::base::WriteStringToFile(invalid_key4, key_file4.path));
+ ASSERT_FALSE(load_keys(key_file4.path, certs));
+
+ // Invalid key separator.
+ std::string invalid_key5 = testkey_v4 + ";" + testkey_v4;
+ TemporaryFile key_file5;
+ ASSERT_TRUE(android::base::WriteStringToFile(invalid_key5, key_file5.path));
+ ASSERT_FALSE(load_keys(key_file5.path, certs));
+}
+
+TEST(VerifierTest, BadPackage_SignatureStartOutOfBounds) {
+ std::string testkey_v3;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
+
+ TemporaryFile key_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file.path));
+ std::vector<Certificate> certs;
+ ASSERT_TRUE(load_keys(key_file.path, certs));
+
+ // Signature start is 65535 (0xffff) while comment size is 0 (Bug: 31914369).
+ std::string package = "\x50\x4b\x05\x06"s + std::string(12, '\0') + "\xff\xff\xff\xff\x00\x00"s;
+ ASSERT_EQ(VERIFY_FAILURE, verify_file(reinterpret_cast<const unsigned char*>(package.data()),
+ package.size(), certs));
+}
+
TEST_P(VerifierSuccessTest, VerifySucceed) {
- ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs), VERIFY_SUCCESS);
+ ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_SUCCESS);
}
TEST_P(VerifierFailureTest, VerifyFailure) {
- ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs), VERIFY_FAILURE);
+ ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_FAILURE);
}
INSTANTIATE_TEST_CASE_P(SingleKeySuccess, VerifierSuccessTest,
- ::testing::Values(
- std::vector<std::string>({"otasigned.zip", "e3"}),
- std::vector<std::string>({"otasigned_f4.zip", "f4"}),
- std::vector<std::string>({"otasigned_sha256.zip", "e3", "sha256"}),
- std::vector<std::string>({"otasigned_f4_sha256.zip", "f4", "sha256"}),
- std::vector<std::string>({"otasigned_ecdsa_sha256.zip", "ec", "sha256"})));
+ ::testing::Values(
+ std::vector<std::string>({"otasigned_v1.zip", "v1"}),
+ std::vector<std::string>({"otasigned_v2.zip", "v2"}),
+ std::vector<std::string>({"otasigned_v3.zip", "v3"}),
+ std::vector<std::string>({"otasigned_v4.zip", "v4"}),
+ std::vector<std::string>({"otasigned_v5.zip", "v5"})));
INSTANTIATE_TEST_CASE_P(MultiKeySuccess, VerifierSuccessTest,
- ::testing::Values(
- std::vector<std::string>({"otasigned.zip", "f4", "e3"}),
- std::vector<std::string>({"otasigned_f4.zip", "ec", "f4"}),
- std::vector<std::string>({"otasigned_sha256.zip", "ec", "e3", "e3", "sha256"}),
- std::vector<std::string>({"otasigned_f4_sha256.zip", "ec", "sha256", "e3", "f4", "sha256"}),
- std::vector<std::string>({"otasigned_ecdsa_sha256.zip", "f4", "sha256", "e3", "ec", "sha256"})));
+ ::testing::Values(
+ std::vector<std::string>({"otasigned_v1.zip", "v1", "v2"}),
+ std::vector<std::string>({"otasigned_v2.zip", "v5", "v2"}),
+ std::vector<std::string>({"otasigned_v3.zip", "v5", "v1", "v3"}),
+ std::vector<std::string>({"otasigned_v4.zip", "v5", "v1", "v4"}),
+ std::vector<std::string>({"otasigned_v5.zip", "v4", "v1", "v5"})));
INSTANTIATE_TEST_CASE_P(WrongKey, VerifierFailureTest,
- ::testing::Values(
- std::vector<std::string>({"otasigned.zip", "f4"}),
- std::vector<std::string>({"otasigned_f4.zip", "e3"}),
- std::vector<std::string>({"otasigned_ecdsa_sha256.zip", "e3", "sha256"})));
+ ::testing::Values(
+ std::vector<std::string>({"otasigned_v1.zip", "v2"}),
+ std::vector<std::string>({"otasigned_v2.zip", "v1"}),
+ std::vector<std::string>({"otasigned_v3.zip", "v5"}),
+ std::vector<std::string>({"otasigned_v4.zip", "v5"}),
+ std::vector<std::string>({"otasigned_v5.zip", "v3"})));
INSTANTIATE_TEST_CASE_P(WrongHash, VerifierFailureTest,
- ::testing::Values(
- std::vector<std::string>({"otasigned.zip", "e3", "sha256"}),
- std::vector<std::string>({"otasigned_f4.zip", "f4", "sha256"}),
- std::vector<std::string>({"otasigned_sha256.zip"}),
- std::vector<std::string>({"otasigned_f4_sha256.zip", "f4"}),
- std::vector<std::string>({"otasigned_ecdsa_sha256.zip"})));
+ ::testing::Values(
+ std::vector<std::string>({"otasigned_v1.zip", "v3"}),
+ std::vector<std::string>({"otasigned_v2.zip", "v4"}),
+ std::vector<std::string>({"otasigned_v3.zip", "v1"}),
+ std::vector<std::string>({"otasigned_v4.zip", "v2"})));
INSTANTIATE_TEST_CASE_P(BadPackage, VerifierFailureTest,
- ::testing::Values(
- std::vector<std::string>({"random.zip"}),
- std::vector<std::string>({"fake-eocd.zip"}),
- std::vector<std::string>({"alter-metadata.zip"}),
- std::vector<std::string>({"alter-footer.zip"})));
+ ::testing::Values(
+ std::vector<std::string>({"random.zip", "v1"}),
+ std::vector<std::string>({"fake-eocd.zip", "v1"}),
+ std::vector<std::string>({"alter-metadata.zip", "v1"}),
+ std::vector<std::string>({"alter-footer.zip", "v1"})));
diff --git a/tests/manual/recovery_test.cpp b/tests/manual/recovery_test.cpp
new file mode 100644
index 000000000..d36dd331e
--- /dev/null
+++ b/tests/manual/recovery_test.cpp
@@ -0,0 +1,224 @@
+/*
+ * 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 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 <dirent.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <android/log.h>
+#include <gtest/gtest.h>
+#include <png.h>
+#include <private/android_logger.h>
+
+#include "minui/minui.h"
+
+static const std::string myFilename = "/data/misc/recovery/inject.txt";
+static const std::string myContent = "Hello World\nWelcome to my recovery\n";
+static const std::string kLocale = "zu";
+static const std::string kResourceTestDir = "/data/nativetest/recovery/";
+
+// Failure is expected on systems that do not deliver either the
+// recovery-persist or recovery-refresh executables. Tests also require
+// a reboot sequence of test to truly verify.
+
+static ssize_t __pmsg_fn(log_id_t logId, char prio, const char *filename,
+ const char *buf, size_t len, void *arg) {
+ EXPECT_EQ(LOG_ID_SYSTEM, logId);
+ EXPECT_EQ(ANDROID_LOG_INFO, prio);
+ EXPECT_NE(std::string::npos, myFilename.find(filename));
+ EXPECT_EQ(myContent, buf);
+ EXPECT_EQ(myContent.size(), len);
+ EXPECT_EQ(nullptr, arg);
+ return len;
+}
+
+// recovery.refresh - May fail. Requires recovery.inject, two reboots,
+// then expect success after second reboot.
+TEST(recovery, refresh) {
+ EXPECT_EQ(0, access("/system/bin/recovery-refresh", F_OK));
+
+ ssize_t ret = __android_log_pmsg_file_read(
+ LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, nullptr);
+ if (ret == -ENOENT) {
+ EXPECT_LT(0, __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
+ myFilename.c_str(), myContent.c_str(), myContent.size()));
+
+ fprintf(stderr, "injected test data, requires two intervening reboots "
+ "to check for replication\n");
+ }
+ EXPECT_EQ(static_cast<ssize_t>(myContent.size()), ret);
+}
+
+// recovery.persist - Requires recovery.inject, then a reboot, then
+// expect success after for this test on that boot.
+TEST(recovery, persist) {
+ EXPECT_EQ(0, access("/system/bin/recovery-persist", F_OK));
+
+ ssize_t ret = __android_log_pmsg_file_read(
+ LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, nullptr);
+ if (ret == -ENOENT) {
+ EXPECT_LT(0, __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
+ myFilename.c_str(), myContent.c_str(), myContent.size()));
+
+ fprintf(stderr, "injected test data, requires intervening reboot "
+ "to check for storage\n");
+ }
+
+ std::string buf;
+ EXPECT_TRUE(android::base::ReadFileToString(myFilename, &buf));
+ EXPECT_EQ(myContent, buf);
+ if (access(myFilename.c_str(), F_OK) == 0) {
+ fprintf(stderr, "Removing persistent test data, "
+ "check if reconstructed on reboot\n");
+ }
+ EXPECT_EQ(0, unlink(myFilename.c_str()));
+}
+
+std::vector<std::string> image_dir {
+ "res-mdpi/images/",
+ "res-hdpi/images/",
+ "res-xhdpi/images/",
+ "res-xxhdpi/images/",
+ "res-xxxhdpi/images/"
+};
+
+static int png_filter(const dirent* de) {
+ if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) {
+ return 0;
+ }
+ return 1;
+}
+
+// Find out all png files to test under /data/nativetest/recovery/.
+static std::vector<std::string> add_files() {
+ std::vector<std::string> files;
+ for (const std::string& str : image_dir) {
+ std::string dir_path = kResourceTestDir + str;
+ dirent** namelist;
+ int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort);
+ if (n == -1) {
+ printf("Failed to scan dir %s: %s\n", kResourceTestDir.c_str(), strerror(errno));
+ return files;
+ }
+ if (n == 0) {
+ printf("No file is added for test in %s\n", kResourceTestDir.c_str());
+ }
+
+ while (n--) {
+ std::string file_path = dir_path + namelist[n]->d_name;
+ files.push_back(file_path);
+ free(namelist[n]);
+ }
+ free(namelist);
+ }
+ return files;
+}
+
+class ResourceTest : public testing::TestWithParam<std::string> {
+ public:
+ static std::vector<std::string> png_list;
+
+ // Parse a png file and test if it's qualified for the background text image
+ // under recovery.
+ void SetUp() override {
+ std::string file_path = GetParam();
+ fp = fopen(file_path.c_str(), "rb");
+ ASSERT_NE(nullptr, fp);
+
+ unsigned char header[8];
+ size_t bytesRead = fread(header, 1, sizeof(header), fp);
+ ASSERT_EQ(sizeof(header), bytesRead);
+ ASSERT_EQ(0, png_sig_cmp(header, 0, sizeof(header)));
+
+ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ ASSERT_NE(nullptr, png_ptr);
+
+ info_ptr = png_create_info_struct(png_ptr);
+ ASSERT_NE(nullptr, info_ptr);
+
+ png_init_io(png_ptr, fp);
+ png_set_sig_bytes(png_ptr, sizeof(header));
+ png_read_info(png_ptr, info_ptr);
+
+ int color_type, bit_depth;
+ png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr,
+ nullptr);
+ ASSERT_EQ(PNG_COLOR_TYPE_GRAY, color_type) << "Recovery expects grayscale PNG file.";
+ ASSERT_LT(static_cast<png_uint_32>(5), width);
+ ASSERT_LT(static_cast<png_uint_32>(0), height);
+ if (bit_depth <= 8) {
+ // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray.
+ png_set_expand_gray_1_2_4_to_8(png_ptr);
+ }
+
+ png_byte channels = png_get_channels(png_ptr, info_ptr);
+ ASSERT_EQ(1, channels) << "Recovery background text images expects 1-channel PNG file.";
+ }
+
+ void TearDown() override {
+ if (png_ptr != nullptr && info_ptr != nullptr) {
+ png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
+ }
+
+ if (fp != nullptr) {
+ fclose(fp);
+ }
+ }
+
+ protected:
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_uint_32 width, height;
+
+ FILE* fp;
+};
+
+std::vector<std::string> ResourceTest::png_list = add_files();
+
+TEST_P(ResourceTest, ValidateLocale) {
+ std::vector<unsigned char> row(width);
+ for (png_uint_32 y = 0; y < height; ++y) {
+ png_read_row(png_ptr, row.data(), nullptr);
+ int w = (row[1] << 8) | row[0];
+ int h = (row[3] << 8) | row[2];
+ int len = row[4];
+ EXPECT_LT(0, w);
+ EXPECT_LT(0, h);
+ EXPECT_LT(0, len) << "Locale string should be non-empty.";
+ EXPECT_NE(0, row[5]) << "Locale string is missing.";
+
+ ASSERT_GT(height, y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
+ char* loc = reinterpret_cast<char*>(&row[5]);
+ if (matches_locale(loc, kLocale.c_str())) {
+ EXPECT_TRUE(android::base::StartsWith(loc, kLocale.c_str()));
+ break;
+ } else {
+ for (int i = 0; i < h; ++i, ++y) {
+ png_read_row(png_ptr, row.data(), nullptr);
+ }
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourceTest,
+ ::testing::ValuesIn(ResourceTest::png_list.cbegin(),
+ ResourceTest::png_list.cend()));
diff --git a/tests/testdata/bonus.file b/tests/testdata/bonus.file
new file mode 100644
index 000000000..918ef8ac5
--- /dev/null
+++ b/tests/testdata/bonus.file
Binary files differ
diff --git a/tests/testdata/boot.img b/tests/testdata/boot.img
new file mode 100644
index 000000000..dd4897510
--- /dev/null
+++ b/tests/testdata/boot.img
Binary files differ
diff --git a/tests/testdata/otasigned.zip b/tests/testdata/otasigned_v1.zip
index a6bc53e41..a6bc53e41 100644
--- a/tests/testdata/otasigned.zip
+++ b/tests/testdata/otasigned_v1.zip
Binary files differ
diff --git a/tests/testdata/otasigned_f4.zip b/tests/testdata/otasigned_v2.zip
index dd1e4dd40..dd1e4dd40 100644
--- a/tests/testdata/otasigned_f4.zip
+++ b/tests/testdata/otasigned_v2.zip
Binary files differ
diff --git a/tests/testdata/otasigned_sha256.zip b/tests/testdata/otasigned_v3.zip
index 0ed4409b3..0ed4409b3 100644
--- a/tests/testdata/otasigned_sha256.zip
+++ b/tests/testdata/otasigned_v3.zip
Binary files differ
diff --git a/tests/testdata/otasigned_f4_sha256.zip b/tests/testdata/otasigned_v4.zip
index 3af408c40..3af408c40 100644
--- a/tests/testdata/otasigned_f4_sha256.zip
+++ b/tests/testdata/otasigned_v4.zip
Binary files differ
diff --git a/tests/testdata/otasigned_ecdsa_sha256.zip b/tests/testdata/otasigned_v5.zip
index 999fcdd0f..999fcdd0f 100644
--- a/tests/testdata/otasigned_ecdsa_sha256.zip
+++ b/tests/testdata/otasigned_v5.zip
Binary files differ
diff --git a/tests/testdata/recovery-from-boot-with-bonus.p b/tests/testdata/recovery-from-boot-with-bonus.p
new file mode 100644
index 000000000..08b6f55e4
--- /dev/null
+++ b/tests/testdata/recovery-from-boot-with-bonus.p
Binary files differ
diff --git a/tests/testdata/recovery-from-boot.p b/tests/testdata/recovery-from-boot.p
new file mode 100644
index 000000000..06f6c299f
--- /dev/null
+++ b/tests/testdata/recovery-from-boot.p
Binary files differ
diff --git a/tests/testdata/recovery.img b/tests/testdata/recovery.img
new file mode 100644
index 000000000..b862e6f0c
--- /dev/null
+++ b/tests/testdata/recovery.img
Binary files differ
diff --git a/tests/testdata/testkey.pk8 b/tests/testdata/testkey_v1.pk8
index 586c1bd5c..586c1bd5c 100644
--- a/tests/testdata/testkey.pk8
+++ b/tests/testdata/testkey_v1.pk8
Binary files differ
diff --git a/tests/testdata/test_key_e3.txt b/tests/testdata/testkey_v1.txt
index 53f5297bd..53f5297bd 100644
--- a/tests/testdata/test_key_e3.txt
+++ b/tests/testdata/testkey_v1.txt
diff --git a/tests/testdata/testkey.x509.pem b/tests/testdata/testkey_v1.x509.pem
index e242d83e2..e242d83e2 100644
--- a/tests/testdata/testkey.x509.pem
+++ b/tests/testdata/testkey_v1.x509.pem
diff --git a/tests/testdata/test_f4.pk8 b/tests/testdata/testkey_v2.pk8
index 3052613c5..3052613c5 100644
--- a/tests/testdata/test_f4.pk8
+++ b/tests/testdata/testkey_v2.pk8
Binary files differ
diff --git a/tests/testdata/test_key_f4.txt b/tests/testdata/testkey_v2.txt
index 54ddbbad1..54ddbbad1 100644
--- a/tests/testdata/test_key_f4.txt
+++ b/tests/testdata/testkey_v2.txt
diff --git a/tests/testdata/test_f4.x509.pem b/tests/testdata/testkey_v2.x509.pem
index 814abcf99..814abcf99 100644
--- a/tests/testdata/test_f4.x509.pem
+++ b/tests/testdata/testkey_v2.x509.pem
diff --git a/tests/testdata/testkey_v3.pk8 b/tests/testdata/testkey_v3.pk8
new file mode 120000
index 000000000..18ecf9815
--- /dev/null
+++ b/tests/testdata/testkey_v3.pk8
@@ -0,0 +1 @@
+testkey_v1.pk8 \ No newline at end of file
diff --git a/tests/testdata/testkey_v3.txt b/tests/testdata/testkey_v3.txt
new file mode 100644
index 000000000..3208571a5
--- /dev/null
+++ b/tests/testdata/testkey_v3.txt
@@ -0,0 +1 @@
+v3 {64,0xc926ad21,{1795090719,2141396315,950055447,2581568430,4268923165,1920809988,546586521,3498997798,1776797858,3740060814,1805317999,1429410244,129622599,1422441418,1783893377,1222374759,2563319927,323993566,28517732,609753416,1826472888,215237850,4261642700,4049082591,3228462402,774857746,154822455,2497198897,2758199418,3019015328,2794777644,87251430,2534927978,120774784,571297800,3695899472,2479925187,3811625450,3401832990,2394869647,3267246207,950095497,555058928,414729973,1136544882,3044590084,465547824,4058146728,2731796054,1689838846,3890756939,1048029507,895090649,247140249,178744550,3547885223,3165179243,109881576,3944604415,1044303212,3772373029,2985150306,3737520932,3599964420},{3437017481,3784475129,2800224972,3086222688,251333580,2131931323,512774938,325948880,2657486437,2102694287,3820568226,792812816,1026422502,2053275343,2800889200,3113586810,165549746,4273519969,4065247892,1902789247,772932719,3941848426,3652744109,216871947,3164400649,1942378755,3996765851,1055777370,964047799,629391717,2232744317,3910558992,191868569,2758883837,3682816752,2997714732,2702529250,3570700455,3776873832,3924067546,3555689545,2758825434,1323144535,61311905,1997411085,376844204,213777604,4077323584,9135381,1625809335,2804742137,2952293945,1117190829,4237312782,1825108855,3013147971,1111251351,2568837572,1684324211,2520978805,367251975,810756730,2353784344,1175080310}}
diff --git a/tests/testdata/testkey_sha256.x509.pem b/tests/testdata/testkey_v3.x509.pem
index 002ce8968..002ce8968 100644
--- a/tests/testdata/testkey_sha256.x509.pem
+++ b/tests/testdata/testkey_v3.x509.pem
diff --git a/tests/testdata/testkey_v4.pk8 b/tests/testdata/testkey_v4.pk8
new file mode 120000
index 000000000..683b9a3f1
--- /dev/null
+++ b/tests/testdata/testkey_v4.pk8
@@ -0,0 +1 @@
+testkey_v2.pk8 \ No newline at end of file
diff --git a/tests/testdata/testkey_v4.txt b/tests/testdata/testkey_v4.txt
new file mode 100644
index 000000000..532cbd51a
--- /dev/null
+++ b/tests/testdata/testkey_v4.txt
@@ -0,0 +1 @@
+v4 {64,0xc9bd1f21,{293133087,3210546773,865313125,250921607,3158780490,943703457,1242806226,2986289859,2942743769,2457906415,2719374299,1783459420,149579627,3081531591,3440738617,2788543742,2758457512,1146764939,3699497403,2446203424,1744968926,1159130537,2370028300,3978231572,3392699980,1487782451,1180150567,2841334302,3753960204,961373345,3333628321,748825784,2978557276,1566596926,1613056060,2600292737,1847226629,50398611,1890374404,2878700735,2286201787,1401186359,619285059,731930817,2340993166,1156490245,2992241729,151498140,318782170,3480838990,2100383433,4223552555,3628927011,4247846280,1759029513,4215632601,2719154626,3490334597,1751299340,3487864726,3668753795,4217506054,3748782284,3150295088},{1772626313,445326068,3477676155,1758201194,2986784722,491035581,3922936562,702212696,2979856666,3324974564,2488428922,3056318590,1626954946,664714029,398585816,3964097931,3356701905,2298377729,2040082097,3025491477,539143308,3348777868,2995302452,3602465520,212480763,2691021393,1307177300,704008044,2031136606,1054106474,3838318865,2441343869,1477566916,700949900,2534790355,3353533667,336163563,4106790558,2701448228,1571536379,1103842411,3623110423,1635278839,1577828979,910322800,715583630,138128831,1017877531,2289162787,447994798,1897243165,4121561445,4150719842,2131821093,2262395396,3305771534,980753571,3256525190,3128121808,1072869975,3507939515,4229109952,118381341,2209831334}}
diff --git a/tests/testdata/test_f4_sha256.x509.pem b/tests/testdata/testkey_v4.x509.pem
index 9d5376b45..9d5376b45 100644
--- a/tests/testdata/test_f4_sha256.x509.pem
+++ b/tests/testdata/testkey_v4.x509.pem
diff --git a/tests/testdata/testkey_ecdsa.pk8 b/tests/testdata/testkey_v5.pk8
index 9a521c8cf..9a521c8cf 100644
--- a/tests/testdata/testkey_ecdsa.pk8
+++ b/tests/testdata/testkey_v5.pk8
Binary files differ
diff --git a/tests/testdata/test_key_ec.txt b/tests/testdata/testkey_v5.txt
index 72b4395d9..72b4395d9 100644
--- a/tests/testdata/test_key_ec.txt
+++ b/tests/testdata/testkey_v5.txt
diff --git a/tests/testdata/testkey_ecdsa.x509.pem b/tests/testdata/testkey_v5.x509.pem
index b12283645..b12283645 100644
--- a/tests/testdata/testkey_ecdsa.x509.pem
+++ b/tests/testdata/testkey_v5.x509.pem
diff --git a/tests/testdata/ziptest_dummy-update.zip b/tests/testdata/ziptest_dummy-update.zip
new file mode 100644
index 000000000..6976bf155
--- /dev/null
+++ b/tests/testdata/ziptest_dummy-update.zip
Binary files differ
diff --git a/tests/testdata/ziptest_valid.zip b/tests/testdata/ziptest_valid.zip
new file mode 100644
index 000000000..9e7cb7800
--- /dev/null
+++ b/tests/testdata/ziptest_valid.zip
Binary files differ
diff --git a/tests/unit/asn1_decoder_test.cpp b/tests/unit/asn1_decoder_test.cpp
index af96d87d2..b334a655b 100644
--- a/tests/unit/asn1_decoder_test.cpp
+++ b/tests/unit/asn1_decoder_test.cpp
@@ -14,225 +14,188 @@
* limitations under the License.
*/
-#define LOG_TAG "asn1_decoder_test"
+#include <stdint.h>
+
+#include <memory>
-#include <cutils/log.h>
#include <gtest/gtest.h>
-#include <stdint.h>
-#include <unistd.h>
#include "asn1_decoder.h"
-namespace android {
-
-class Asn1DecoderTest : public testing::Test {
-};
-
-TEST_F(Asn1DecoderTest, Empty_Failure) {
- uint8_t empty[] = { };
- asn1_context_t* ctx = asn1_context_new(empty, sizeof(empty));
-
- EXPECT_EQ(NULL, asn1_constructed_get(ctx));
- EXPECT_FALSE(asn1_constructed_skip_all(ctx));
- EXPECT_EQ(0, asn1_constructed_type(ctx));
- EXPECT_EQ(NULL, asn1_sequence_get(ctx));
- EXPECT_EQ(NULL, asn1_set_get(ctx));
- EXPECT_FALSE(asn1_sequence_next(ctx));
-
- uint8_t* junk;
- size_t length;
- EXPECT_FALSE(asn1_oid_get(ctx, &junk, &length));
- EXPECT_FALSE(asn1_octet_string_get(ctx, &junk, &length));
-
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) {
- uint8_t truncated[] = { 0xA0, 0x82, };
- asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
- EXPECT_EQ(NULL, asn1_constructed_get(ctx));
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) {
- uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A,
- 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, };
- asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
- EXPECT_EQ(NULL, asn1_constructed_get(ctx));
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) {
- uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01, };
- asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
- asn1_context_t* ptr = asn1_constructed_get(ctx);
- ASSERT_NE((asn1_context_t*)NULL, ptr);
- EXPECT_EQ(5, asn1_constructed_type(ptr));
- uint8_t* oid;
- size_t length;
- EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
- asn1_context_free(ptr);
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, ConstructedGet_Success) {
- uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01, };
- asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
- asn1_context_t* ptr = asn1_constructed_get(ctx);
- ASSERT_NE((asn1_context_t*)NULL, ptr);
- EXPECT_EQ(5, asn1_constructed_type(ptr));
- uint8_t* oid;
- size_t length;
- ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
- EXPECT_EQ(1U, length);
- EXPECT_EQ(0x01U, *oid);
- asn1_context_free(ptr);
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) {
- uint8_t truncated[] = { 0xA2, 0x82, };
- asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
- EXPECT_FALSE(asn1_constructed_skip_all(ctx));
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, ConstructedSkipAll_Success) {
- uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01,
- 0xA1, 0x03, 0x02, 0x01, 0x01,
- 0x06, 0x01, 0xA5, };
- asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
- ASSERT_TRUE(asn1_constructed_skip_all(ctx));
- uint8_t* oid;
- size_t length;
- ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
- EXPECT_EQ(1U, length);
- EXPECT_EQ(0xA5U, *oid);
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) {
- uint8_t truncated[] = { 0x30, 0x82, };
- asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
- EXPECT_EQ(NULL, asn1_sequence_get(ctx));
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) {
- uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01, };
- asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
- asn1_context_t* ptr = asn1_sequence_get(ctx);
- ASSERT_NE((asn1_context_t*)NULL, ptr);
- uint8_t* oid;
- size_t length;
- EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
- asn1_context_free(ptr);
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, SequenceGet_Success) {
- uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01, };
- asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
- asn1_context_t* ptr = asn1_sequence_get(ctx);
- ASSERT_NE((asn1_context_t*)NULL, ptr);
- uint8_t* oid;
- size_t length;
- ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
- EXPECT_EQ(1U, length);
- EXPECT_EQ(0x01U, *oid);
- asn1_context_free(ptr);
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, SetGet_TruncatedLength_Failure) {
- uint8_t truncated[] = { 0x31, 0x82, };
- asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
- EXPECT_EQ(NULL, asn1_set_get(ctx));
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) {
- uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01, };
- asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
- asn1_context_t* ptr = asn1_set_get(ctx);
- ASSERT_NE((asn1_context_t*)NULL, ptr);
- uint8_t* oid;
- size_t length;
- EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
- asn1_context_free(ptr);
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, SetGet_Success) {
- uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA, };
- asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
- asn1_context_t* ptr = asn1_set_get(ctx);
- ASSERT_NE((asn1_context_t*)NULL, ptr);
- uint8_t* oid;
- size_t length;
- ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
- EXPECT_EQ(1U, length);
- EXPECT_EQ(0xBAU, *oid);
- asn1_context_free(ptr);
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, OidGet_LengthZero_Failure) {
- uint8_t data[] = { 0x06, 0x00, 0x01, };
- asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
- uint8_t* oid;
- size_t length;
- EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, OidGet_TooSmall_Failure) {
- uint8_t data[] = { 0x06, 0x01, };
- asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
- uint8_t* oid;
- size_t length;
- EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, OidGet_Success) {
- uint8_t data[] = { 0x06, 0x01, 0x99, };
- asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
- uint8_t* oid;
- size_t length;
- ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
- EXPECT_EQ(1U, length);
- EXPECT_EQ(0x99U, *oid);
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) {
- uint8_t data[] = { 0x04, 0x00, 0x55, };
- asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
- uint8_t* string;
- size_t length;
- ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) {
- uint8_t data[] = { 0x04, 0x01, };
- asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
- uint8_t* string;
- size_t length;
- ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
- asn1_context_free(ctx);
-}
-
-TEST_F(Asn1DecoderTest, OctetStringGet_Success) {
- uint8_t data[] = { 0x04, 0x01, 0xAA, };
- asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
- uint8_t* string;
- size_t length;
- ASSERT_TRUE(asn1_octet_string_get(ctx, &string, &length));
- EXPECT_EQ(1U, length);
- EXPECT_EQ(0xAAU, *string);
- asn1_context_free(ctx);
-}
-
-} // namespace android
+TEST(Asn1DecoderTest, Empty_Failure) {
+ uint8_t empty[] = {};
+ asn1_context ctx(empty, sizeof(empty));
+
+ ASSERT_EQ(nullptr, ctx.asn1_constructed_get());
+ ASSERT_FALSE(ctx.asn1_constructed_skip_all());
+ ASSERT_EQ(0, ctx.asn1_constructed_type());
+ ASSERT_EQ(nullptr, ctx.asn1_sequence_get());
+ ASSERT_EQ(nullptr, ctx.asn1_set_get());
+ ASSERT_FALSE(ctx.asn1_sequence_next());
+
+ const uint8_t* junk;
+ size_t length;
+ ASSERT_FALSE(ctx.asn1_oid_get(&junk, &length));
+ ASSERT_FALSE(ctx.asn1_octet_string_get(&junk, &length));
+}
+
+TEST(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) {
+ uint8_t truncated[] = { 0xA0, 0x82 };
+ asn1_context ctx(truncated, sizeof(truncated));
+ ASSERT_EQ(nullptr, ctx.asn1_constructed_get());
+}
+
+TEST(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) {
+ uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A };
+ asn1_context ctx(truncated, sizeof(truncated));
+ ASSERT_EQ(nullptr, ctx.asn1_constructed_get());
+}
+
+TEST(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) {
+ uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01 };
+ asn1_context ctx(data, sizeof(data));
+ std::unique_ptr<asn1_context> ptr(ctx.asn1_constructed_get());
+ ASSERT_NE(nullptr, ptr);
+ ASSERT_EQ(5, ptr->asn1_constructed_type());
+ const uint8_t* oid;
+ size_t length;
+ ASSERT_FALSE(ptr->asn1_oid_get(&oid, &length));
+}
+
+TEST(Asn1DecoderTest, ConstructedGet_Success) {
+ uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01 };
+ asn1_context ctx(data, sizeof(data));
+ std::unique_ptr<asn1_context> ptr(ctx.asn1_constructed_get());
+ ASSERT_NE(nullptr, ptr);
+ ASSERT_EQ(5, ptr->asn1_constructed_type());
+ const uint8_t* oid;
+ size_t length;
+ ASSERT_TRUE(ptr->asn1_oid_get(&oid, &length));
+ ASSERT_EQ(1U, length);
+ ASSERT_EQ(0x01U, *oid);
+}
+
+TEST(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) {
+ uint8_t truncated[] = { 0xA2, 0x82 };
+ asn1_context ctx(truncated, sizeof(truncated));
+ ASSERT_FALSE(ctx.asn1_constructed_skip_all());
+}
+
+TEST(Asn1DecoderTest, ConstructedSkipAll_Success) {
+ uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01, 0xA1, 0x03, 0x02, 0x01, 0x01, 0x06, 0x01, 0xA5 };
+ asn1_context ctx(data, sizeof(data));
+ ASSERT_TRUE(ctx.asn1_constructed_skip_all());
+ const uint8_t* oid;
+ size_t length;
+ ASSERT_TRUE(ctx.asn1_oid_get(&oid, &length));
+ ASSERT_EQ(1U, length);
+ ASSERT_EQ(0xA5U, *oid);
+}
+
+TEST(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) {
+ uint8_t truncated[] = { 0x30, 0x82 };
+ asn1_context ctx(truncated, sizeof(truncated));
+ ASSERT_EQ(nullptr, ctx.asn1_sequence_get());
+}
+
+TEST(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) {
+ uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01 };
+ asn1_context ctx(data, sizeof(data));
+ std::unique_ptr<asn1_context> ptr(ctx.asn1_sequence_get());
+ ASSERT_NE(nullptr, ptr);
+ const uint8_t* oid;
+ size_t length;
+ ASSERT_FALSE(ptr->asn1_oid_get(&oid, &length));
+}
+
+TEST(Asn1DecoderTest, SequenceGet_Success) {
+ uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01 };
+ asn1_context ctx(data, sizeof(data));
+ std::unique_ptr<asn1_context> ptr(ctx.asn1_sequence_get());
+ ASSERT_NE(nullptr, ptr);
+ const uint8_t* oid;
+ size_t length;
+ ASSERT_TRUE(ptr->asn1_oid_get(&oid, &length));
+ ASSERT_EQ(1U, length);
+ ASSERT_EQ(0x01U, *oid);
+}
+
+TEST(Asn1DecoderTest, SetGet_TruncatedLength_Failure) {
+ uint8_t truncated[] = { 0x31, 0x82 };
+ asn1_context ctx(truncated, sizeof(truncated));
+ ASSERT_EQ(nullptr, ctx.asn1_set_get());
+}
+
+TEST(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) {
+ uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01 };
+ asn1_context ctx(data, sizeof(data));
+ std::unique_ptr<asn1_context> ptr(ctx.asn1_set_get());
+ ASSERT_NE(nullptr, ptr);
+ const uint8_t* oid;
+ size_t length;
+ ASSERT_FALSE(ptr->asn1_oid_get(&oid, &length));
+}
+
+TEST(Asn1DecoderTest, SetGet_Success) {
+ uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA };
+ asn1_context ctx(data, sizeof(data));
+ std::unique_ptr<asn1_context> ptr(ctx.asn1_set_get());
+ ASSERT_NE(nullptr, ptr);
+ const uint8_t* oid;
+ size_t length;
+ ASSERT_TRUE(ptr->asn1_oid_get(&oid, &length));
+ ASSERT_EQ(1U, length);
+ ASSERT_EQ(0xBAU, *oid);
+}
+
+TEST(Asn1DecoderTest, OidGet_LengthZero_Failure) {
+ uint8_t data[] = { 0x06, 0x00, 0x01 };
+ asn1_context ctx(data, sizeof(data));
+ const uint8_t* oid;
+ size_t length;
+ ASSERT_FALSE(ctx.asn1_oid_get(&oid, &length));
+}
+
+TEST(Asn1DecoderTest, OidGet_TooSmall_Failure) {
+ uint8_t data[] = { 0x06, 0x01 };
+ asn1_context ctx(data, sizeof(data));
+ const uint8_t* oid;
+ size_t length;
+ ASSERT_FALSE(ctx.asn1_oid_get(&oid, &length));
+}
+
+TEST(Asn1DecoderTest, OidGet_Success) {
+ uint8_t data[] = { 0x06, 0x01, 0x99 };
+ asn1_context ctx(data, sizeof(data));
+ const uint8_t* oid;
+ size_t length;
+ ASSERT_TRUE(ctx.asn1_oid_get(&oid, &length));
+ ASSERT_EQ(1U, length);
+ ASSERT_EQ(0x99U, *oid);
+}
+
+TEST(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) {
+ uint8_t data[] = { 0x04, 0x00, 0x55 };
+ asn1_context ctx(data, sizeof(data));
+ const uint8_t* string;
+ size_t length;
+ ASSERT_FALSE(ctx.asn1_octet_string_get(&string, &length));
+}
+
+TEST(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) {
+ uint8_t data[] = { 0x04, 0x01 };
+ asn1_context ctx(data, sizeof(data));
+ const uint8_t* string;
+ size_t length;
+ ASSERT_FALSE(ctx.asn1_octet_string_get(&string, &length));
+}
+
+TEST(Asn1DecoderTest, OctetStringGet_Success) {
+ uint8_t data[] = { 0x04, 0x01, 0xAA };
+ asn1_context ctx(data, sizeof(data));
+ const uint8_t* string;
+ size_t length;
+ ASSERT_TRUE(ctx.asn1_octet_string_get(&string, &length));
+ ASSERT_EQ(1U, length);
+ ASSERT_EQ(0xAAU, *string);
+}
diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp
new file mode 100644
index 000000000..5e2ae4fb5
--- /dev/null
+++ b/tests/unit/dirutil_test.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright 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 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 <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <otautil/DirUtil.h>
+
+TEST(DirUtilTest, create_invalid) {
+ // Requesting to create an empty dir is invalid.
+ ASSERT_EQ(-1, dirCreateHierarchy("", 0755, nullptr, false, nullptr));
+ ASSERT_EQ(ENOENT, errno);
+
+ // Requesting to strip the name with no slash present.
+ ASSERT_EQ(-1, dirCreateHierarchy("abc", 0755, nullptr, true, nullptr));
+ ASSERT_EQ(ENOENT, errno);
+
+ // Creating a dir that already exists.
+ TemporaryDir td;
+ ASSERT_EQ(0, dirCreateHierarchy(td.path, 0755, nullptr, false, nullptr));
+
+ // "///" is a valid dir.
+ ASSERT_EQ(0, dirCreateHierarchy("///", 0755, nullptr, false, nullptr));
+
+ // Request to create a dir, but a file with the same name already exists.
+ TemporaryFile tf;
+ ASSERT_EQ(-1, dirCreateHierarchy(tf.path, 0755, nullptr, false, nullptr));
+ ASSERT_EQ(ENOTDIR, errno);
+}
+
+TEST(DirUtilTest, create_smoke) {
+ TemporaryDir td;
+ std::string prefix(td.path);
+ std::string path = prefix + "/a/b";
+ constexpr mode_t mode = 0755;
+ ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), mode, nullptr, false, nullptr));
+
+ // Verify.
+ struct stat sb;
+ ASSERT_EQ(0, stat(path.c_str(), &sb)) << strerror(errno);
+ ASSERT_TRUE(S_ISDIR(sb.st_mode));
+ constexpr mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO;
+ ASSERT_EQ(mode, sb.st_mode & mask);
+
+ // Clean up.
+ ASSERT_EQ(0, rmdir((prefix + "/a/b").c_str()));
+ ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
+}
+
+TEST(DirUtilTest, create_strip_filename) {
+ TemporaryDir td;
+ std::string prefix(td.path);
+ std::string path = prefix + "/a/b";
+ ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), 0755, nullptr, true, nullptr));
+
+ // Verify that "../a" exists but not "../a/b".
+ struct stat sb;
+ ASSERT_EQ(0, stat((prefix + "/a").c_str(), &sb)) << strerror(errno);
+ ASSERT_TRUE(S_ISDIR(sb.st_mode));
+
+ ASSERT_EQ(-1, stat(path.c_str(), &sb));
+ ASSERT_EQ(ENOENT, errno);
+
+ // Clean up.
+ ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
+}
+
+TEST(DirUtilTest, create_mode_and_timestamp) {
+ TemporaryDir td;
+ std::string prefix(td.path);
+ std::string path = prefix + "/a/b";
+ // Set the timestamp to 8/1/2008.
+ constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };
+ constexpr mode_t mode = 0751;
+ ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), mode, &timestamp, false, nullptr));
+
+ // Verify the mode and timestamp for "../a/b".
+ struct stat sb;
+ ASSERT_EQ(0, stat(path.c_str(), &sb)) << strerror(errno);
+ ASSERT_TRUE(S_ISDIR(sb.st_mode));
+ constexpr mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO;
+ ASSERT_EQ(mode, sb.st_mode & mask);
+
+ timespec time;
+ time.tv_sec = 1217592000;
+ time.tv_nsec = 0;
+
+ ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+ ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+ // Verify the mode for "../a". Note that the timestamp for intermediate directories (e.g. "../a")
+ // may not be 'timestamp' according to the current implementation.
+ ASSERT_EQ(0, stat((prefix + "/a").c_str(), &sb)) << strerror(errno);
+ ASSERT_TRUE(S_ISDIR(sb.st_mode));
+ ASSERT_EQ(mode, sb.st_mode & mask);
+
+ // Clean up.
+ ASSERT_EQ(0, rmdir((prefix + "/a/b").c_str()));
+ ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
+}
+
+TEST(DirUtilTest, unlink_invalid) {
+ // File doesn't exist.
+ ASSERT_EQ(-1, dirUnlinkHierarchy("doesntexist"));
+
+ // Nonexistent directory.
+ TemporaryDir td;
+ std::string path(td.path);
+ ASSERT_EQ(-1, dirUnlinkHierarchy((path + "/a").c_str()));
+ ASSERT_EQ(ENOENT, errno);
+}
+
+TEST(DirUtilTest, unlink_smoke) {
+ // Unlink a file.
+ TemporaryFile tf;
+ ASSERT_EQ(0, dirUnlinkHierarchy(tf.path));
+ ASSERT_EQ(-1, access(tf.path, F_OK));
+
+ TemporaryDir td;
+ std::string path(td.path);
+ constexpr mode_t mode = 0700;
+ ASSERT_EQ(0, mkdir((path + "/a").c_str(), mode));
+ ASSERT_EQ(0, mkdir((path + "/a/b").c_str(), mode));
+ ASSERT_EQ(0, mkdir((path + "/a/b/c").c_str(), mode));
+ ASSERT_EQ(0, mkdir((path + "/a/d").c_str(), mode));
+
+ // Remove "../a" recursively.
+ ASSERT_EQ(0, dirUnlinkHierarchy((path + "/a").c_str()));
+
+ // Verify it's gone.
+ ASSERT_EQ(-1, access((path + "/a").c_str(), F_OK));
+}
diff --git a/tests/unit/locale_test.cpp b/tests/unit/locale_test.cpp
index 0e515f8c1..cdaba0e8b 100644
--- a/tests/unit/locale_test.cpp
+++ b/tests/unit/locale_test.cpp
@@ -19,11 +19,15 @@
#include "minui/minui.h"
TEST(LocaleTest, Misc) {
- EXPECT_TRUE(matches_locale("zh_CN", "zh_CN_#Hans"));
- EXPECT_TRUE(matches_locale("zh", "zh_CN_#Hans"));
- EXPECT_FALSE(matches_locale("zh_HK", "zh_CN_#Hans"));
- EXPECT_TRUE(matches_locale("en_GB", "en_GB"));
- EXPECT_TRUE(matches_locale("en", "en_GB"));
- EXPECT_FALSE(matches_locale("en_GB", "en"));
- EXPECT_FALSE(matches_locale("en_GB", "en_US"));
+ EXPECT_TRUE(matches_locale("zh-CN", "zh-Hans-CN"));
+ EXPECT_TRUE(matches_locale("zh", "zh-Hans-CN"));
+ EXPECT_FALSE(matches_locale("zh-HK", "zh-Hans-CN"));
+ EXPECT_TRUE(matches_locale("en-GB", "en-GB"));
+ EXPECT_TRUE(matches_locale("en", "en-GB"));
+ EXPECT_FALSE(matches_locale("en-GB", "en"));
+ EXPECT_FALSE(matches_locale("en-GB", "en-US"));
+ EXPECT_FALSE(matches_locale("en-US", ""));
+ // Empty locale prefix in the PNG file will match the input locale.
+ EXPECT_TRUE(matches_locale("", "en-US"));
+ EXPECT_TRUE(matches_locale("sr-Latn", "sr-Latn-BA"));
}
diff --git a/tests/unit/recovery_test.cpp b/tests/unit/recovery_test.cpp
deleted file mode 100644
index f397f258e..000000000
--- a/tests/unit/recovery_test.cpp
+++ /dev/null
@@ -1,92 +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 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 <fcntl.h>
-#include <string.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <android/log.h>
-#include <gtest/gtest.h>
-#include <log/logger.h>
-#include <private/android_logger.h>
-
-static const char myFilename[] = "/data/misc/recovery/inject.txt";
-static const char myContent[] = "Hello World\nWelcome to my recovery\n";
-
-// Failure is expected on systems that do not deliver either the
-// recovery-persist or recovery-refresh executables. Tests also require
-// a reboot sequence of test to truly verify.
-
-static ssize_t __pmsg_fn(log_id_t logId, char prio, const char *filename,
- const char *buf, size_t len, void *arg) {
- EXPECT_EQ(LOG_ID_SYSTEM, logId);
- EXPECT_EQ(ANDROID_LOG_INFO, prio);
- EXPECT_EQ(0, NULL == strstr(myFilename,filename));
- EXPECT_EQ(0, strcmp(myContent, buf));
- EXPECT_EQ(sizeof(myContent), len);
- EXPECT_EQ(0, NULL != arg);
- return len;
-}
-
-// recovery.refresh - May fail. Requires recovery.inject, two reboots,
-// then expect success after second reboot.
-TEST(recovery, refresh) {
- EXPECT_EQ(0, access("/system/bin/recovery-refresh", F_OK));
-
- ssize_t ret = __android_log_pmsg_file_read(
- LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, NULL);
- if (ret == -ENOENT) {
- EXPECT_LT(0, __android_log_pmsg_file_write(
- LOG_ID_SYSTEM, ANDROID_LOG_INFO,
- myFilename, myContent, sizeof(myContent)));
- fprintf(stderr, "injected test data, "
- "requires two intervening reboots "
- "to check for replication\n");
- }
- EXPECT_EQ((ssize_t)sizeof(myContent), ret);
-}
-
-// recovery.persist - Requires recovery.inject, then a reboot, then
-// expect success after for this test on that boot.
-TEST(recovery, persist) {
- EXPECT_EQ(0, access("/system/bin/recovery-persist", F_OK));
-
- ssize_t ret = __android_log_pmsg_file_read(
- LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, NULL);
- if (ret == -ENOENT) {
- EXPECT_LT(0, __android_log_pmsg_file_write(
- LOG_ID_SYSTEM, ANDROID_LOG_INFO,
- myFilename, myContent, sizeof(myContent)));
- fprintf(stderr, "injected test data, "
- "requires intervening reboot "
- "to check for storage\n");
- }
-
- int fd = open(myFilename, O_RDONLY);
- EXPECT_LE(0, fd);
-
- char buf[sizeof(myContent) + 32];
- ret = read(fd, buf, sizeof(buf));
- close(fd);
- EXPECT_EQ(ret, (ssize_t)sizeof(myContent));
- EXPECT_EQ(0, strcmp(myContent, buf));
- if (fd >= 0) {
- fprintf(stderr, "Removing persistent test data, "
- "check if reconstructed on reboot\n");
- }
- EXPECT_EQ(0, unlink(myFilename));
-}
diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp
new file mode 100644
index 000000000..f4699664b
--- /dev/null
+++ b/tests/unit/sysutil_test.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright 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 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 <gtest/gtest.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+
+#include "otautil/SysUtil.h"
+
+TEST(SysUtilTest, InvalidArgs) {
+ MemMapping mapping;
+
+ // Invalid argument.
+ ASSERT_EQ(-1, sysMapFile(nullptr, &mapping));
+ ASSERT_EQ(-1, sysMapFile("/somefile", nullptr));
+}
+
+TEST(SysUtilTest, sysMapFileRegularFile) {
+ TemporaryFile temp_file1;
+ std::string content = "abc";
+ ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file1.path));
+
+ // sysMapFile() should map the file to one range.
+ MemMapping mapping;
+ ASSERT_EQ(0, sysMapFile(temp_file1.path, &mapping));
+ ASSERT_NE(nullptr, mapping.addr);
+ ASSERT_EQ(content.size(), mapping.length);
+ ASSERT_EQ(1U, mapping.ranges.size());
+
+ sysReleaseMap(&mapping);
+ ASSERT_EQ(0U, mapping.ranges.size());
+}
+
+TEST(SysUtilTest, sysMapFileBlockMap) {
+ // Create a file that has 10 blocks.
+ TemporaryFile package;
+ std::string content;
+ constexpr size_t file_size = 4096 * 10;
+ content.reserve(file_size);
+ ASSERT_TRUE(android::base::WriteStringToFile(content, package.path));
+
+ TemporaryFile block_map_file;
+ std::string filename = std::string("@") + block_map_file.path;
+ MemMapping mapping;
+
+ // One range.
+ std::string block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10\n";
+ ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path));
+
+ ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping));
+ ASSERT_EQ(file_size, mapping.length);
+ ASSERT_EQ(1U, mapping.ranges.size());
+
+ // It's okay to not have the trailing '\n'.
+ block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10";
+ ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path));
+
+ ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping));
+ ASSERT_EQ(file_size, mapping.length);
+ ASSERT_EQ(1U, mapping.ranges.size());
+
+ // Or having multiple trailing '\n's.
+ block_map_content = std::string(package.path) + "\n40960 4096\n1\n0 10\n\n\n";
+ ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path));
+
+ ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping));
+ ASSERT_EQ(file_size, mapping.length);
+ ASSERT_EQ(1U, mapping.ranges.size());
+
+ // Multiple ranges.
+ block_map_content = std::string(package.path) + "\n40960 4096\n3\n0 3\n3 5\n5 10\n";
+ ASSERT_TRUE(android::base::WriteStringToFile(block_map_content, block_map_file.path));
+
+ ASSERT_EQ(0, sysMapFile(filename.c_str(), &mapping));
+ ASSERT_EQ(file_size, mapping.length);
+ ASSERT_EQ(3U, mapping.ranges.size());
+
+ sysReleaseMap(&mapping);
+ ASSERT_EQ(0U, mapping.ranges.size());
+}
+
+TEST(SysUtilTest, sysMapFileBlockMapInvalidBlockMap) {
+ MemMapping mapping;
+ TemporaryFile temp_file;
+ std::string filename = std::string("@") + temp_file.path;
+
+ // Block map file is too short.
+ ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n", temp_file.path));
+ ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+ ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n0\n", temp_file.path));
+ ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+ // Block map file has unexpected number of lines.
+ ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n1\n", temp_file.path));
+ ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+ ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n2\n0 1\n", temp_file.path));
+ ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+ // Invalid size/blksize/range_count.
+ ASSERT_TRUE(android::base::WriteStringToFile("/somefile\nabc 4096\n1\n0 1\n", temp_file.path));
+ ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+ ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n\n0 1\n", temp_file.path));
+ ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+ // size/blksize/range_count don't match.
+ ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n0 4096\n1\n0 1\n", temp_file.path));
+ ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+ ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 0\n1\n0 1\n", temp_file.path));
+ ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+ ASSERT_TRUE(android::base::WriteStringToFile("/somefile\n4096 4096\n0\n0 1\n", temp_file.path));
+ ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+ // Invalid block dev path.
+ ASSERT_TRUE(android::base::WriteStringToFile("/doesntexist\n4096 4096\n1\n0 1\n", temp_file.path));
+ ASSERT_EQ(-1, sysMapFile(filename.c_str(), &mapping));
+
+ sysReleaseMap(&mapping);
+ ASSERT_EQ(0U, mapping.ranges.size());
+}
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
new file mode 100644
index 000000000..4a1a49b97
--- /dev/null
+++ b/tests/unit/zip_test.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 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 <errno.h>
+#include <unistd.h>
+
+#include <memory>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <otautil/SysUtil.h>
+#include <otautil/ZipUtil.h>
+#include <ziparchive/zip_archive.h>
+
+#include "common/test_constants.h"
+
+TEST(ZipTest, ExtractPackageRecursive) {
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Extract the whole package into a temp directory.
+ TemporaryDir td;
+ ASSERT_NE(nullptr, td.path);
+ ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
+
+ // Make sure all the files are extracted correctly.
+ std::string path(td.path);
+ ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
+
+ // The content of the file is the same as expected.
+ std::string content1;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
+ ASSERT_EQ(kATxtContents, content1);
+
+ std::string content2;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
+ ASSERT_EQ(kDTxtContents, content2);
+
+ CloseArchive(handle);
+
+ // Clean up.
+ ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
+ ASSERT_EQ(0, rmdir((path + "/b").c_str()));
+}
+
+TEST(ZipTest, OpenFromMemory) {
+ MemMapping map;
+ std::string zip_path = from_testdata_base("ziptest_dummy-update.zip");
+ ASSERT_EQ(0, sysMapFile(zip_path.c_str(), &map));
+
+ // Map an update package into memory and open the archive from there.
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_path.c_str(), &handle));
+
+ static constexpr const char* BINARY_PATH = "META-INF/com/google/android/update-binary";
+ ZipString binary_path(BINARY_PATH);
+ ZipEntry binary_entry;
+ // Make sure the package opens correctly and its entry can be read.
+ ASSERT_EQ(0, FindEntry(handle, binary_path, &binary_entry));
+
+ TemporaryFile tmp_binary;
+ ASSERT_NE(-1, tmp_binary.fd);
+ ASSERT_EQ(0, ExtractEntryToFile(handle, &binary_entry, tmp_binary.fd));
+
+ CloseArchive(handle);
+ sysReleaseMap(&map);
+}
+
diff --git a/tests/unit/ziputil_test.cpp b/tests/unit/ziputil_test.cpp
new file mode 100644
index 000000000..14e541690
--- /dev/null
+++ b/tests/unit/ziputil_test.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright 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 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 <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <otautil/ZipUtil.h>
+#include <ziparchive/zip_archive.h>
+
+#include "common/test_constants.h"
+
+TEST(ZipUtilTest, invalid_args) {
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // zip_path must be a relative path.
+ ASSERT_FALSE(ExtractPackageRecursive(handle, "/a/b", "/tmp", nullptr, nullptr));
+
+ // dest_path must be an absolute path.
+ ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "tmp", nullptr, nullptr));
+ ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "", nullptr, nullptr));
+
+ CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_all) {
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Extract the whole package into a temp directory.
+ TemporaryDir td;
+ ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
+
+ // Make sure all the files are extracted correctly.
+ std::string path(td.path);
+ ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
+
+ // The content of the file is the same as expected.
+ std::string content1;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
+ ASSERT_EQ(kATxtContents, content1);
+
+ std::string content2;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
+ ASSERT_EQ(kDTxtContents, content2);
+
+ // Clean up the temp files under td.
+ ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
+ ASSERT_EQ(0, rmdir((path + "/b").c_str()));
+
+ CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_prefix_with_slash) {
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Extract all the entries starting with "b/".
+ TemporaryDir td;
+ ExtractPackageRecursive(handle, "b/", td.path, nullptr, nullptr);
+
+ // Make sure all the files with "b/" prefix are extracted correctly.
+ std::string path(td.path);
+ ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
+
+ // And the rest are not extracted.
+ ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+ ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+
+ // The content of the file is the same as expected.
+ std::string content1;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
+ ASSERT_EQ(kCTxtContents, content1);
+
+ std::string content2;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
+ ASSERT_EQ(kDTxtContents, content2);
+
+ // Clean up the temp files under td.
+ ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
+
+ CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_prefix_without_slash) {
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Extract all the file entries starting with "b/".
+ TemporaryDir td;
+ ExtractPackageRecursive(handle, "b", td.path, nullptr, nullptr);
+
+ // Make sure all the files with "b/" prefix are extracted correctly.
+ std::string path(td.path);
+ ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
+
+ // And the rest are not extracted.
+ ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+ ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+
+ // The content of the file is the same as expected.
+ std::string content1;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
+ ASSERT_EQ(kCTxtContents, content1);
+
+ std::string content2;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
+ ASSERT_EQ(kDTxtContents, content2);
+
+ // Clean up the temp files under td.
+ ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
+
+ CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, set_timestamp) {
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Set the timestamp to 8/1/2008.
+ constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };
+
+ // Extract all the entries starting with "b/".
+ TemporaryDir td;
+ ExtractPackageRecursive(handle, "b", td.path, &timestamp, nullptr);
+
+ // Make sure all the files with "b/" prefix are extracted correctly.
+ std::string path(td.path);
+ std::string file_c = path + "/c.txt";
+ std::string file_d = path + "/d.txt";
+ ASSERT_EQ(0, access(file_c.c_str(), F_OK));
+ ASSERT_EQ(0, access(file_d.c_str(), F_OK));
+
+ // Verify the timestamp.
+ timespec time;
+ time.tv_sec = 1217592000;
+ time.tv_nsec = 0;
+
+ struct stat sb;
+ ASSERT_EQ(0, stat(file_c.c_str(), &sb)) << strerror(errno);
+ ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+ ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+ ASSERT_EQ(0, stat(file_d.c_str(), &sb)) << strerror(errno);
+ ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+ ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+ // Clean up the temp files under td.
+ ASSERT_EQ(0, unlink(file_c.c_str()));
+ ASSERT_EQ(0, unlink(file_d.c_str()));
+
+ CloseArchive(handle);
+}