summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Android.bp205
-rw-r--r--tests/Android.mk223
-rw-r--r--tests/AndroidTest.xml10
-rw-r--r--tests/common/test_constants.h15
-rw-r--r--tests/component/applypatch_modes_test.cpp199
-rw-r--r--tests/component/applypatch_test.cpp387
-rw-r--r--tests/component/edify_test.cpp75
-rw-r--r--tests/component/imgdiff_test.cpp107
-rw-r--r--tests/component/install_test.cpp174
-rw-r--r--tests/component/resources_test.cpp120
-rw-r--r--tests/component/update_verifier_test.cpp215
-rw-r--r--tests/component/updater_test.cpp1028
-rw-r--r--tests/component/verifier_test.cpp4
-rw-r--r--tests/manual/recovery_test.cpp181
-rw-r--r--tests/testdata/font.pngbin0 -> 24437 bytes
-rw-r--r--tests/testdata/gzipped_sourcebin0 -> 1436 bytes
-rw-r--r--tests/testdata/gzipped_targetbin0 -> 1502 bytes
-rw-r--r--tests/testdata/loop00000.pngbin0 -> 5900 bytes
-rw-r--r--tests/testdata/new.filebin1388877 -> 0 bytes
-rw-r--r--tests/testdata/old.filebin1348051 -> 0 bytes
-rw-r--r--tests/unit/applypatch_test.cpp291
-rw-r--r--tests/unit/commands_test.cpp554
-rw-r--r--tests/unit/dirutil_test.cpp3
-rw-r--r--tests/unit/parse_install_logs_test.cpp75
-rw-r--r--tests/unit/rangeset_test.cpp3
-rw-r--r--tests/unit/screen_ui_test.cpp494
-rw-r--r--tests/unit/sysutil_test.cpp15
-rw-r--r--tests/unit/zip_test.cpp2
28 files changed, 3014 insertions, 1366 deletions
diff --git a/tests/Android.bp b/tests/Android.bp
new file mode 100644
index 000000000..2cfc32572
--- /dev/null
+++ b/tests/Android.bp
@@ -0,0 +1,205 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_defaults {
+ name: "recovery_test_defaults",
+
+ defaults: [
+ "recovery_defaults",
+ ],
+
+ include_dirs: [
+ "bootable/recovery",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libcrypto",
+ "libcutils",
+ "liblog",
+ "libpng",
+ "libselinux",
+ "libz",
+ "libziparchive",
+ ],
+
+ target: {
+ android: {
+ shared_libs: [
+ "libutils",
+ ],
+ },
+
+ host: {
+ static_libs: [
+ "libutils",
+ ],
+ },
+ },
+}
+
+// libapplypatch, libapplypatch_modes, libimgdiff, libimgpatch
+libapplypatch_static_libs = [
+ "libapplypatch_modes",
+ "libapplypatch",
+ "libedify",
+ "libimgdiff",
+ "libimgpatch",
+ "libotautil",
+ "libbsdiff",
+ "libbspatch",
+ "libdivsufsort",
+ "libdivsufsort64",
+ "libutils",
+ "libbase",
+ "libbrotli",
+ "libbz",
+ "libcrypto",
+ "libz",
+ "libziparchive",
+]
+
+// librecovery_defaults uses many shared libs that we want to avoid using in tests (e.g. we don't
+// have 32-bit android.hardware.health@2.0.so or libbootloader_message.so on marlin).
+librecovery_static_libs = [
+ "librecovery",
+ "librecovery_fastboot",
+ "libminui",
+ "libverifier",
+ "libotautil",
+
+ "libhealthhalutils",
+ "libvintf_recovery",
+ "libvintf",
+
+ "android.hardware.health@2.0",
+ "android.hardware.health@1.0",
+ "libbootloader_message",
+ "libext4_utils",
+ "libfs_mgr",
+ "libfusesideload",
+ "libhidl-gen-utils",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "libbinderthreadstate",
+ "libvndksupport",
+ "libtinyxml2",
+]
+
+cc_test {
+ name: "recovery_unit_test",
+ isolated: true,
+
+ defaults: [
+ "recovery_test_defaults",
+ ],
+
+ test_suites: ["device-tests"],
+
+ srcs: [
+ "unit/*.cpp",
+ ],
+
+ static_libs: libapplypatch_static_libs + [
+ "librecovery_ui",
+ "libminui",
+ "libverifier",
+ "libotautil",
+ "libupdater",
+ "libgtest_prod",
+ ],
+
+ data: ["testdata/*"],
+}
+
+cc_test {
+ name: "recovery_manual_test",
+ isolated: true,
+
+ defaults: [
+ "recovery_test_defaults",
+ ],
+
+ test_suites: ["device-tests"],
+
+ srcs: [
+ "manual/recovery_test.cpp",
+ ],
+}
+
+cc_test {
+ name: "recovery_component_test",
+ isolated: true,
+
+ defaults: [
+ "recovery_test_defaults",
+ "libupdater_defaults",
+ ],
+
+ test_suites: ["device-tests"],
+
+ srcs: [
+ "component/*.cpp",
+ ],
+
+ static_libs: libapplypatch_static_libs + librecovery_static_libs + [
+ "libupdater",
+ "libupdate_verifier",
+ "libprotobuf-cpp-lite",
+ ],
+
+ data: [
+ "testdata/*",
+ ":res-testdata",
+ ],
+}
+
+cc_test_host {
+ name: "recovery_host_test",
+ isolated: true,
+
+ defaults: [
+ "recovery_test_defaults",
+ ],
+
+ srcs: [
+ "component/imgdiff_test.cpp",
+ ],
+
+ static_libs: [
+ "libimgdiff",
+ "libimgpatch",
+ "libotautil",
+ "libbsdiff",
+ "libbspatch",
+ "libziparchive",
+ "libutils",
+ "libcrypto",
+ "libbrotli",
+ "libbz",
+ "libdivsufsort64",
+ "libdivsufsort",
+ "libz",
+ ],
+
+ data: ["testdata/*"],
+
+ target: {
+ darwin: {
+ // libimgdiff is not available on the Mac.
+ enabled: false,
+ },
+ },
+}
diff --git a/tests/Android.mk b/tests/Android.mk
deleted file mode 100644
index b3584fe87..000000000
--- a/tests/Android.mk
+++ /dev/null
@@ -1,223 +0,0 @@
-#
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-# Unit tests
-include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_MODULE := recovery_unit_test
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_STATIC_LIBRARIES := \
- libverifier \
- libminui \
- libotautil \
- libupdater \
- libziparchive \
- libutils \
- libz \
- libselinux \
- libbase \
- libBionicGtestMain
-
-LOCAL_SRC_FILES := \
- unit/asn1_decoder_test.cpp \
- unit/dirutil_test.cpp \
- unit/locale_test.cpp \
- unit/rangeset_test.cpp \
- unit/sysutil_test.cpp \
- unit/zip_test.cpp \
-
-LOCAL_C_INCLUDES := bootable/recovery
-LOCAL_SHARED_LIBRARIES := liblog
-include $(BUILD_NATIVE_TEST)
-
-# Manual tests
-include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_MODULE := recovery_manual_test
-LOCAL_STATIC_LIBRARIES := \
- libminui \
- libbase \
- libBionicGtestMain
-
-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 := \
- -Wall \
- -Werror \
- -D_FILE_OFFSET_BITS=64
-
-ifeq ($(AB_OTA_UPDATER),true)
-LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
-endif
-
-ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true)
-LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1
-endif
-
-ifeq ($(BOARD_AVB_ENABLE),true)
-LOCAL_CFLAGS += -DBOARD_AVB_ENABLE=1
-endif
-
-LOCAL_MODULE := recovery_component_test
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_C_INCLUDES := bootable/recovery
-LOCAL_SRC_FILES := \
- 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/update_verifier_test.cpp \
- component/verifier_test.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- libhidlbase
-
-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 \
- libfusesideload \
- libotafault \
- librecovery \
- libupdater \
- libbootloader_message \
- libverifier \
- libotautil \
- libmounts \
- libupdate_verifier \
- libdivsufsort \
- libdivsufsort64 \
- libfs_mgr \
- libvintf_recovery \
- libvintf \
- libhidl-gen-utils \
- libtinyxml2 \
- libselinux \
- libext4_utils \
- libsparse \
- libcrypto_utils \
- libcrypto \
- libbz \
- libziparchive \
- liblog \
- libutils \
- libz \
- libbase \
- libtune2fs \
- libfec \
- libfec_rs \
- libsquashfs_utils \
- libcutils \
- libbrotli \
- libBionicGtestMain \
- $(tune2fs_static_libraries)
-
-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 := -Wall -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 \
- libotautil \
- libbsdiff \
- libbspatch \
- libziparchive \
- libutils \
- libbase \
- libcrypto \
- libbrotli \
- libbz \
- libdivsufsort64 \
- libdivsufsort \
- libz \
- libBionicGtestMain
-LOCAL_SHARED_LIBRARIES := \
- liblog
-include $(BUILD_HOST_NATIVE_TEST)
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
index 3999aa57d..6b86085aa 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -16,16 +16,18 @@
<configuration description="Config for recovery_component_test and recovery_unit_test">
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
- <option name="push" value="recovery_component_test->/data/local/tmp/recovery_component_test" />
- <option name="push" value="recovery_unit_test->/data/local/tmp/recovery_unit_test" />
+ <option name="push" value="recovery_component_test->/data/local/tmp/recovery_component_test/recovery_component_test" />
+ <option name="push" value="testdata->/data/local/tmp/recovery_component_test/testdata" />
+ <option name="push" value="recovery_unit_test->/data/local/tmp/recovery_unit_test/recovery_unit_test" />
+ <option name="push" value="testdata->/data/local/tmp/recovery_unit_test/testdata" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
<test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="native-test-device-path" value="/data/local/tmp/recovery_component_test" />
<option name="module-name" value="recovery_component_test" />
</test>
<test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="native-test-device-path" value="/data/local/tmp/recovery_unit_test" />
<option name="module-name" value="recovery_unit_test" />
</test>
</configuration>
diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h
index 514818e0a..b6c27a754 100644
--- a/tests/common/test_constants.h
+++ b/tests/common/test_constants.h
@@ -17,10 +17,10 @@
#ifndef _OTA_TEST_CONSTANTS_H
#define _OTA_TEST_CONSTANTS_H
-#include <stdlib.h>
-
#include <string>
+#include <android-base/file.h>
+
// Zip entries in ziptest_valid.zip.
static const std::string kATxtContents("abcdefghabcdefgh\n");
static const std::string kBTxtContents("abcdefgh\n");
@@ -32,14 +32,9 @@ static const std::string kATxtSha1Sum("32c96a03dc8cd20097940f351bca6261ee5a1643"
// echo -n -e "abcdefgh\n" | sha1sum
static const std::string kBTxtSha1Sum("e414af7161c9554089f4106d6f1797ef14a73666");
-static std::string from_testdata_base(const std::string& fname) {
-#ifdef __ANDROID__
- static std::string data_root = getenv("ANDROID_DATA");
-#else
- static std::string data_root = std::string(getenv("ANDROID_PRODUCT_OUT")) + "/data";
-#endif
-
- return data_root + "/nativetest/recovery/testdata/" + fname;
+[[maybe_unused]] static std::string from_testdata_base(const std::string& fname) {
+ static std::string exec_dir = android::base::GetExecutableDirectory();
+ return exec_dir + "/testdata/" + fname;
}
#endif // _OTA_TEST_CONSTANTS_H
diff --git a/tests/component/applypatch_modes_test.cpp b/tests/component/applypatch_modes_test.cpp
new file mode 100644
index 000000000..ce01f4fd5
--- /dev/null
+++ b/tests/component/applypatch_modes_test.cpp
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agree to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+#include <bsdiff/bsdiff.h>
+#include <gtest/gtest.h>
+#include <openssl/sha.h>
+
+#include "applypatch/applypatch_modes.h"
+#include "common/test_constants.h"
+#include "otautil/paths.h"
+#include "otautil/print_sha1.h"
+#include "otautil/sysutil.h"
+
+using namespace std::string_literals;
+
+// Loads a given partition and returns a string of form "EMMC:name:size:hash".
+static std::string GetEmmcTargetString(const std::string& filename,
+ const std::string& display_name = "") {
+ std::string data;
+ if (!android::base::ReadFileToString(filename, &data)) {
+ PLOG(ERROR) << "Failed to read " << filename;
+ return {};
+ }
+
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest);
+
+ return "EMMC:"s + (display_name.empty() ? filename : display_name) + ":" +
+ std::to_string(data.size()) + ":" + print_sha1(digest);
+}
+
+class ApplyPatchModesTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ source = GetEmmcTargetString(from_testdata_base("boot.img"));
+ ASSERT_FALSE(source.empty());
+
+ std::string recovery_file = from_testdata_base("recovery.img");
+ recovery = GetEmmcTargetString(recovery_file);
+ ASSERT_FALSE(recovery.empty());
+
+ ASSERT_TRUE(android::base::WriteStringToFile("", patched_file_.path));
+ target = GetEmmcTargetString(recovery_file, patched_file_.path);
+ ASSERT_FALSE(target.empty());
+
+ Paths::Get().set_cache_temp_source(cache_source_.path);
+ }
+
+ std::string source;
+ std::string target;
+ std::string recovery;
+
+ private:
+ TemporaryFile cache_source_;
+ TemporaryFile patched_file_;
+};
+
+static int InvokeApplyPatchModes(const std::vector<std::string>& args) {
+ auto args_to_call = StringVectorToNullTerminatedArray(args);
+ return applypatch_modes(args_to_call.size() - 1, args_to_call.data());
+}
+
+static void VerifyPatchedTarget(const std::string& target) {
+ std::vector<std::string> pieces = android::base::Split(target, ":");
+ ASSERT_EQ(4, pieces.size());
+ ASSERT_EQ("EMMC", pieces[0]);
+
+ std::string patched_emmc = GetEmmcTargetString(pieces[1]);
+ ASSERT_FALSE(patched_emmc.empty());
+ ASSERT_EQ(target, patched_emmc);
+}
+
+TEST_F(ApplyPatchModesTest, InvalidArgs) {
+ // At least two args (including the filename).
+ ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch" }));
+
+ // Unrecognized args.
+ ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch", "-x" }));
+}
+
+TEST_F(ApplyPatchModesTest, PatchModeEmmcTarget) {
+ std::vector<std::string> args{
+ "applypatch",
+ "--bonus",
+ from_testdata_base("bonus.file"),
+ "--patch",
+ from_testdata_base("recovery-from-boot.p"),
+ "--target",
+ target,
+ "--source",
+ source,
+ };
+ ASSERT_EQ(0, InvokeApplyPatchModes(args));
+ VerifyPatchedTarget(target);
+}
+
+// Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has
+// everything).
+TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithoutBonusFile) {
+ std::vector<std::string> args{
+ "applypatch", "--patch", from_testdata_base("recovery-from-boot-with-bonus.p"),
+ "--target", target, "--source",
+ source,
+ };
+
+ ASSERT_EQ(0, InvokeApplyPatchModes(args));
+ VerifyPatchedTarget(target);
+}
+
+// Ensures that applypatch works with a bsdiff based recovery-from-boot.p.
+TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithBsdiffPatch) {
+ // Generate the bsdiff patch of recovery-from-boot.p.
+ std::string src_content;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("boot.img"), &src_content));
+
+ std::string tgt_content;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("recovery.img"), &tgt_content));
+
+ TemporaryFile patch_file;
+ ASSERT_EQ(0,
+ bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()), src_content.size(),
+ reinterpret_cast<const uint8_t*>(tgt_content.data()), tgt_content.size(),
+ patch_file.path, nullptr));
+
+ std::vector<std::string> args{
+ "applypatch", "--patch", patch_file.path, "--target", target, "--source", source,
+ };
+ ASSERT_EQ(0, InvokeApplyPatchModes(args));
+ VerifyPatchedTarget(target);
+}
+
+TEST_F(ApplyPatchModesTest, PatchModeInvalidArgs) {
+ // Invalid bonus file.
+ std::vector<std::string> args{
+ "applypatch", "--bonus", "/doesntexist", "--patch", from_testdata_base("recovery-from-boot.p"),
+ "--target", target, "--source", source,
+ };
+ ASSERT_NE(0, InvokeApplyPatchModes(args));
+
+ // With bonus file, but missing args.
+ ASSERT_NE(0,
+ InvokeApplyPatchModes({ "applypatch", "--bonus", from_testdata_base("bonus.file") }));
+}
+
+TEST_F(ApplyPatchModesTest, FlashMode) {
+ std::vector<std::string> args{
+ "applypatch", "--flash", from_testdata_base("recovery.img"), "--target", target,
+ };
+ ASSERT_EQ(0, InvokeApplyPatchModes(args));
+ VerifyPatchedTarget(target);
+}
+
+TEST_F(ApplyPatchModesTest, FlashModeInvalidArgs) {
+ std::vector<std::string> args{
+ "applypatch", "--bonus", from_testdata_base("bonus.file"), "--flash", source,
+ "--target", target,
+ };
+ ASSERT_NE(0, InvokeApplyPatchModes(args));
+}
+
+TEST_F(ApplyPatchModesTest, CheckMode) {
+ ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--check", recovery }));
+ ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--check", source }));
+}
+
+TEST_F(ApplyPatchModesTest, CheckModeInvalidArgs) {
+ ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch", "--check" }));
+}
+
+TEST_F(ApplyPatchModesTest, CheckModeNonEmmcTarget) {
+ ASSERT_NE(0, InvokeApplyPatchModes({ "applypatch", "--check", from_testdata_base("boot.img") }));
+}
+
+TEST_F(ApplyPatchModesTest, ShowLicenses) {
+ ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--license" }));
+}
diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp
deleted file mode 100644
index 61e06adb6..000000000
--- a/tests/component/applypatch_test.cpp
+++ /dev/null
@@ -1,387 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agree to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <fcntl.h>
-#include <gtest/gtest.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <sys/statvfs.h>
-#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 <bsdiff/bsdiff.h>
-#include <openssl/sha.h>
-
-#include "applypatch/applypatch.h"
-#include "applypatch/applypatch_modes.h"
-#include "common/test_constants.h"
-#include "otautil/cache_location.h"
-#include "otautil/print_sha1.h"
-
-using namespace std::string_literals;
-
-static void sha1sum(const std::string& fname, std::string* sha1, size_t* fsize = nullptr) {
- ASSERT_NE(nullptr, sha1);
-
- std::string data;
- ASSERT_TRUE(android::base::ReadFileToString(fname, &data));
-
- if (fsize != nullptr) {
- *fsize = data.size();
- }
-
- uint8_t digest[SHA_DIGEST_LENGTH];
- SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest);
- *sha1 = print_sha1(digest);
-}
-
-static void mangle_file(const std::string& fname) {
- std::string content(1024, '\0');
- for (size_t i = 0; i < 1024; i++) {
- content[i] = rand() % 256;
- }
- ASSERT_TRUE(android::base::WriteStringToFile(content, fname));
-}
-
-class ApplyPatchTest : public ::testing::Test {
- public:
- virtual void SetUp() override {
- // set up files
- old_file = from_testdata_base("old.file");
- new_file = from_testdata_base("new.file");
- nonexistent_file = from_testdata_base("nonexistent.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());
- }
-
- std::string old_file;
- std::string new_file;
- std::string nonexistent_file;
-
- std::string old_sha1;
- std::string new_sha1;
- std::string bad_sha1_a;
- std::string bad_sha1_b;
-
- size_t old_size;
- size_t new_size;
-};
-
-class ApplyPatchCacheTest : public ApplyPatchTest {
- protected:
- void SetUp() override {
- ApplyPatchTest::SetUp();
- CacheLocation::location().set_cache_temp_source(old_file);
- }
-};
-
-class ApplyPatchModesTest : public ::testing::Test {
- protected:
- void SetUp() override {
- CacheLocation::location().set_cache_temp_source(cache_source.path);
- }
-
- TemporaryFile cache_source;
-};
-
-TEST_F(ApplyPatchTest, CheckModeSkip) {
- std::vector<std::string> sha1s;
- ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
-}
-
-TEST_F(ApplyPatchTest, CheckModeSingle) {
- std::vector<std::string> sha1s = { old_sha1 };
- ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
-}
-
-TEST_F(ApplyPatchTest, CheckModeMultiple) {
- 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) {
- 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, CheckCacheCorruptedSourceSingle) {
- TemporaryFile temp_file;
- mangle_file(temp_file.path);
- std::vector<std::string> sha1s_single = { old_sha1 };
- ASSERT_EQ(0, applypatch_check(temp_file.path, sha1s_single));
- ASSERT_EQ(0, applypatch_check(nonexistent_file.c_str(), sha1s_single));
-}
-
-TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceMultiple) {
- TemporaryFile temp_file;
- mangle_file(temp_file.path);
- std::vector<std::string> sha1s_multiple = { bad_sha1_a, old_sha1, bad_sha1_b };
- ASSERT_EQ(0, applypatch_check(temp_file.path, sha1s_multiple));
- ASSERT_EQ(0, applypatch_check(nonexistent_file.c_str(), sha1s_multiple));
-}
-
-TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceFailure) {
- TemporaryFile temp_file;
- mangle_file(temp_file.path);
- std::vector<std::string> sha1s_failure = { bad_sha1_a, bad_sha1_b };
- ASSERT_NE(0, applypatch_check(temp_file.path, sha1s_failure));
- ASSERT_NE(0, applypatch_check(nonexistent_file.c_str(), sha1s_failure));
-}
-
-TEST_F(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(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()));
-}
-
-// Ensures that applypatch works with a bsdiff based recovery-from-boot.p.
-TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithBsdiffPatch) {
- std::string boot_img_file = from_testdata_base("boot.img");
- std::string boot_img_sha1;
- size_t boot_img_size;
- sha1sum(boot_img_file, &boot_img_sha1, &boot_img_size);
-
- std::string recovery_img_file = from_testdata_base("recovery.img");
- std::string recovery_img_sha1;
- size_t recovery_img_size;
- sha1sum(recovery_img_file, &recovery_img_sha1, &recovery_img_size);
-
- // Generate the bsdiff patch of recovery-from-boot.p.
- std::string src_content;
- ASSERT_TRUE(android::base::ReadFileToString(boot_img_file, &src_content));
-
- std::string tgt_content;
- ASSERT_TRUE(android::base::ReadFileToString(recovery_img_file, &tgt_content));
-
- TemporaryFile patch_file;
- ASSERT_EQ(0,
- bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()), src_content.size(),
- reinterpret_cast<const uint8_t*>(tgt_content.data()), tgt_content.size(),
- patch_file.path, nullptr));
-
- // applypatch <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
- std::string src_file_arg =
- "EMMC:" + boot_img_file + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1;
- TemporaryFile tgt_file;
- std::string tgt_file_arg = "EMMC:"s + tgt_file.path;
- std::string recovery_img_size_arg = std::to_string(recovery_img_size);
- std::string patch_arg = boot_img_sha1 + ":" + patch_file.path;
- std::vector<const char*> args = { "applypatch",
- src_file_arg.c_str(),
- tgt_file_arg.c_str(),
- recovery_img_sha1.c_str(),
- recovery_img_size_arg.c_str(),
- patch_arg.c_str() };
- ASSERT_EQ(0, applypatch_modes(args.size(), args.data()));
-
- // Double check the patched recovery image.
- std::string tgt_file_sha1;
- size_t tgt_file_size;
- sha1sum(tgt_file.path, &tgt_file_sha1, &tgt_file_size);
- ASSERT_EQ(recovery_img_size, tgt_file_size);
- ASSERT_EQ(recovery_img_sha1, tgt_file_sha1);
-}
-
-TEST_F(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(ApplyPatchModesTest, CheckModeInvalidArgs) {
- // Insufficient args.
- ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" }));
-}
-
-TEST_F(ApplyPatchModesTest, ShowLicenses) {
- ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" }));
-}
diff --git a/tests/component/edify_test.cpp b/tests/component/edify_test.cpp
index 61a1e6b64..8397bd38e 100644
--- a/tests/component/edify_test.cpp
+++ b/tests/component/edify_test.cpp
@@ -21,30 +21,29 @@
#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());
- }
-
+static void expect(const std::string& expr_str, const char* expected) {
+ std::unique_ptr<Expr> e;
+ int error_count = 0;
+ EXPECT_EQ(0, ParseString(expr_str, &e, &error_count));
+ EXPECT_EQ(0, error_count);
+
+ State state(expr_str, nullptr);
+
+ std::string result;
+ bool status = Evaluate(&state, e, &result);
+
+ if (expected == nullptr) {
+ EXPECT_FALSE(status);
+ } else {
+ EXPECT_STREQ(expected, result.c_str());
+ }
}
class EdifyTest : public ::testing::Test {
- protected:
- virtual void SetUp() {
- RegisterBuiltins();
- }
+ protected:
+ void SetUp() {
+ RegisterBuiltins();
+ }
};
TEST_F(EdifyTest, parsing) {
@@ -146,25 +145,23 @@ TEST_F(EdifyTest, comparison) {
}
TEST_F(EdifyTest, big_string) {
- // big string
- expect(std::string(8192, 's').c_str(), std::string(8192, 's').c_str());
+ expect(std::string(8192, 's'), 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);
+ const char* script1 = "unknown_function()";
+ std::unique_ptr<Expr> expr;
+ int error_count = 0;
+ EXPECT_EQ(1, ParseString(script1, &expr, &error_count));
+ EXPECT_EQ(1, error_count);
+
+ const char* script2 = "abc; unknown_function()";
+ error_count = 0;
+ EXPECT_EQ(1, ParseString(script2, &expr, &error_count));
+ EXPECT_EQ(1, error_count);
+
+ const char* script3 = "unknown_function1() || yes";
+ error_count = 0;
+ EXPECT_EQ(1, ParseString(script3, &expr, &error_count));
+ EXPECT_EQ(1, error_count);
}
diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp
index 6c23def01..cb4868a4a 100644
--- a/tests/component/imgdiff_test.cpp
+++ b/tests/component/imgdiff_test.cpp
@@ -197,12 +197,17 @@ TEST(ImgdiffTest, zip_mode_smoke_store) {
}
TEST(ImgdiffTest, zip_mode_smoke_compressed) {
+ // Generate 1 block of random data.
+ std::string random_data;
+ random_data.reserve(4096);
+ generate_n(back_inserter(random_data), 4096, []() { return rand() % 256; });
+
// Construct src and tgt zip files.
TemporaryFile src_file;
FILE* src_file_ptr = fdopen(src_file.release(), "wb");
ZipWriter src_writer(src_file_ptr);
ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
- const std::string src_content("abcdefg");
+ const std::string src_content = random_data;
ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
ASSERT_EQ(0, src_writer.FinishEntry());
ASSERT_EQ(0, src_writer.Finish());
@@ -212,7 +217,7 @@ TEST(ImgdiffTest, zip_mode_smoke_compressed) {
FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
ZipWriter tgt_writer(tgt_file_ptr);
ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
- const std::string tgt_content("abcdefgxyz");
+ const std::string tgt_content = random_data + "extra contents";
ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
ASSERT_EQ(0, tgt_writer.FinishEntry());
ASSERT_EQ(0, tgt_writer.Finish());
@@ -245,13 +250,57 @@ TEST(ImgdiffTest, zip_mode_smoke_compressed) {
verify_patched_image(src, patch, tgt);
}
+TEST(ImgdiffTest, zip_mode_empty_target) {
+ TemporaryFile src_file;
+ FILE* src_file_ptr = fdopen(src_file.release(), "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));
+
+ // Construct a empty entry in the target zip.
+ TemporaryFile tgt_file;
+ FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
+ ZipWriter tgt_writer(tgt_file_ptr);
+ ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+ const std::string tgt_content;
+ ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+ ASSERT_EQ(0, tgt_writer.FinishEntry());
+ ASSERT_EQ(0, tgt_writer.Finish());
+
+ // 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));
+
+ verify_patched_image(src, patch, tgt);
+}
+
TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
+ // Generate 1 block of random data.
+ std::string random_data;
+ random_data.reserve(4096);
+ generate_n(back_inserter(random_data), 4096, []() { return rand() % 256; });
+
// Construct src and tgt zip files.
TemporaryFile src_file;
FILE* src_file_ptr = fdopen(src_file.release(), "wb");
ZipWriter src_writer(src_file_ptr);
ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
- const std::string src_content("abcdefg");
+ const std::string src_content = random_data;
ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
ASSERT_EQ(0, src_writer.FinishEntry());
ASSERT_EQ(0, src_writer.Finish());
@@ -261,7 +310,7 @@ TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
ZipWriter tgt_writer(tgt_file_ptr);
ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
- const std::string tgt_content("abcdefgxyz");
+ const std::string tgt_content = random_data + "abcdefg";
ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
ASSERT_EQ(0, tgt_writer.FinishEntry());
ASSERT_EQ(0, tgt_writer.Finish());
@@ -298,23 +347,19 @@ TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
}
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());
+ std::string gzipped_source_path = from_testdata_base("gzipped_source");
+ std::string gzipped_source;
+ ASSERT_TRUE(android::base::ReadFileToString(gzipped_source_path, &gzipped_source));
+
+ const std::string src = "abcdefg" + gzipped_source;
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());
+ std::string gzipped_target_path = from_testdata_base("gzipped_target");
+ std::string gzipped_target;
+ ASSERT_TRUE(android::base::ReadFileToString(gzipped_target_path, &gzipped_target));
+ const std::string tgt = "abcdefgxyz" + gzipped_target;
+
TemporaryFile tgt_file;
ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
@@ -404,23 +449,21 @@ TEST(ImgdiffTest, image_mode_different_num_chunks) {
}
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());
+ // src: "abcdefg" + gzipped_source.
+ std::string gzipped_source_path = from_testdata_base("gzipped_source");
+ std::string gzipped_source;
+ ASSERT_TRUE(android::base::ReadFileToString(gzipped_source_path, &gzipped_source));
+
+ const std::string src = "abcdefg" + gzipped_source;
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());
+ // tgt: gzipped_target + "abcdefgxyz".
+ std::string gzipped_target_path = from_testdata_base("gzipped_target");
+ std::string gzipped_target;
+ ASSERT_TRUE(android::base::ReadFileToString(gzipped_target_path, &gzipped_target));
+
+ const std::string tgt = gzipped_target + "abcdefgxyz";
TemporaryFile tgt_file;
ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp
index d19d788e4..08b429000 100644
--- a/tests/component/install_test.cpp
+++ b/tests/component/install_test.cpp
@@ -33,6 +33,7 @@
#include <ziparchive/zip_writer.h>
#include "install.h"
+#include "otautil/paths.h"
#include "private/install.h"
TEST(InstallTest, verify_package_compatibility_no_entry) {
@@ -199,8 +200,73 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml
CloseArchive(zip);
}
-#ifdef AB_OTA_UPDATER
-static void VerifyAbUpdateBinaryCommand(const std::string& serialno, bool success = true) {
+TEST(InstallTest, SetUpNonAbUpdateCommands) {
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.release(), "w");
+ ZipWriter writer(zip_file);
+ static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
+ ASSERT_EQ(0, writer.StartEntry(UPDATE_BINARY_NAME, kCompressStored));
+ 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 package = "/path/to/update.zip";
+ TemporaryDir td;
+ std::string binary_path = std::string(td.path) + "/update_binary";
+ Paths::Get().set_temporary_update_binary(binary_path);
+ std::vector<std::string> cmd;
+ ASSERT_EQ(0, SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
+ ASSERT_EQ(4U, cmd.size());
+ ASSERT_EQ(binary_path, cmd[0]);
+ ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION
+ ASSERT_EQ(std::to_string(status_fd), cmd[2]);
+ ASSERT_EQ(package, cmd[3]);
+ struct stat sb;
+ ASSERT_EQ(0, stat(binary_path.c_str(), &sb));
+ ASSERT_EQ(static_cast<mode_t>(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+
+ // With non-zero retry count. update_binary will be removed automatically.
+ cmd.clear();
+ ASSERT_EQ(0, SetUpNonAbUpdateCommands(package, zip, 2, status_fd, &cmd));
+ ASSERT_EQ(5U, cmd.size());
+ ASSERT_EQ(binary_path, cmd[0]);
+ ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION
+ ASSERT_EQ(std::to_string(status_fd), cmd[2]);
+ ASSERT_EQ(package, cmd[3]);
+ ASSERT_EQ("retry", cmd[4]);
+ sb = {};
+ ASSERT_EQ(0, stat(binary_path.c_str(), &sb));
+ ASSERT_EQ(static_cast<mode_t>(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, SetUpNonAbUpdateCommands_MissingUpdateBinary) {
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.release(), "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));
+
+ // Missing update binary.
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ int status_fd = 10;
+ std::string package = "/path/to/update.zip";
+ TemporaryDir td;
+ Paths::Get().set_temporary_update_binary(std::string(td.path) + "/update_binary");
+ std::vector<std::string> cmd;
+ ASSERT_EQ(INSTALL_CORRUPT, SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
+ CloseArchive(zip);
+}
+
+static void VerifyAbUpdateCommands(const std::string& serialno, bool success = true) {
TemporaryFile temp_file;
FILE* zip_file = fdopen(temp_file.release(), "w");
ZipWriter writer(zip_file);
@@ -235,73 +301,27 @@ static void VerifyAbUpdateBinaryCommand(const std::string& serialno, bool succes
ASSERT_EQ(0, FindEntry(zip, payload_name, &payload_entry));
int status_fd = 10;
std::string package = "/path/to/update.zip";
- std::string binary_path = "/sbin/update_engine_sideload";
std::vector<std::string> cmd;
if (success) {
- ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
+ ASSERT_EQ(0, SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
ASSERT_EQ(5U, cmd.size());
- ASSERT_EQ(binary_path, cmd[0]);
+ ASSERT_EQ("/system/bin/update_engine_sideload", cmd[0]);
ASSERT_EQ("--payload=file://" + package, cmd[1]);
ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]);
ASSERT_EQ("--headers=" + properties, cmd[3]);
ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
} else {
- ASSERT_EQ(INSTALL_ERROR, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
+ ASSERT_EQ(INSTALL_ERROR, SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
}
CloseArchive(zip);
}
-#endif // AB_OTA_UPDATER
-TEST(InstallTest, update_binary_command_smoke) {
-#ifdef AB_OTA_UPDATER
+TEST(InstallTest, SetUpAbUpdateCommands) {
// Empty serialno will pass the verification.
- VerifyAbUpdateBinaryCommand({});
-#else
- TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
- ZipWriter writer(zip_file);
- static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
- ASSERT_EQ(0, writer.StartEntry(UPDATE_BINARY_NAME, kCompressStored));
- 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 package = "/path/to/update.zip";
- TemporaryDir td;
- std::string binary_path = std::string(td.path) + "/update_binary";
- std::vector<std::string> cmd;
- ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
- ASSERT_EQ(4U, cmd.size());
- ASSERT_EQ(binary_path, cmd[0]);
- ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION
- ASSERT_EQ(std::to_string(status_fd), cmd[2]);
- ASSERT_EQ(package, cmd[3]);
- struct stat sb;
- ASSERT_EQ(0, stat(binary_path.c_str(), &sb));
- ASSERT_EQ(static_cast<mode_t>(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
-
- // With non-zero retry count. update_binary will be removed automatically.
- cmd.clear();
- ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 2, status_fd, &cmd));
- ASSERT_EQ(5U, cmd.size());
- ASSERT_EQ(binary_path, cmd[0]);
- ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION
- ASSERT_EQ(std::to_string(status_fd), cmd[2]);
- ASSERT_EQ(package, cmd[3]);
- ASSERT_EQ("retry", cmd[4]);
- sb = {};
- ASSERT_EQ(0, stat(binary_path.c_str(), &sb));
- ASSERT_EQ(static_cast<mode_t>(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
-
- CloseArchive(zip);
-#endif // AB_OTA_UPDATER
+ VerifyAbUpdateCommands({});
}
-TEST(InstallTest, update_binary_command_invalid) {
-#ifdef AB_OTA_UPDATER
+TEST(InstallTest, SetUpAbUpdateCommands_MissingPayloadPropertiesTxt) {
TemporaryFile temp_file;
FILE* zip_file = fdopen(temp_file.release(), "w");
ZipWriter writer(zip_file);
@@ -328,60 +348,36 @@ TEST(InstallTest, update_binary_command_invalid) {
ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
int status_fd = 10;
std::string package = "/path/to/update.zip";
- std::string binary_path = "/sbin/update_engine_sideload";
- std::vector<std::string> cmd;
- ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
- CloseArchive(zip);
-#else
- TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "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));
-
- // Missing update binary.
- ZipArchiveHandle zip;
- ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
- int status_fd = 10;
- std::string package = "/path/to/update.zip";
- TemporaryDir td;
- std::string binary_path = std::string(td.path) + "/update_binary";
std::vector<std::string> cmd;
- ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
+ ASSERT_EQ(INSTALL_CORRUPT, SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
CloseArchive(zip);
-#endif // AB_OTA_UPDATER
}
-#ifdef AB_OTA_UPDATER
-TEST(InstallTest, update_binary_command_multiple_serialno) {
+TEST(InstallTest, SetUpAbUpdateCommands_MultipleSerialnos) {
std::string serialno = android::base::GetProperty("ro.serialno", "");
ASSERT_NE("", serialno);
// Single matching serialno will pass the verification.
- VerifyAbUpdateBinaryCommand(serialno);
+ VerifyAbUpdateCommands(serialno);
static constexpr char alphabet[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
auto generator = []() { return alphabet[rand() % (sizeof(alphabet) - 1)]; };
// Generate 900 random serial numbers.
- std::string random_serial;
+ std::string random_serialno;
for (size_t i = 0; i < 900; i++) {
- generate_n(back_inserter(random_serial), serialno.size(), generator);
- random_serial.append("|");
+ generate_n(back_inserter(random_serialno), serialno.size(), generator);
+ random_serialno.append("|");
}
// Random serialnos should fail the verification.
- VerifyAbUpdateBinaryCommand(random_serial, false);
+ VerifyAbUpdateCommands(random_serialno, false);
- std::string long_serial = random_serial + serialno + "|";
+ std::string long_serialno = random_serialno + serialno + "|";
for (size_t i = 0; i < 99; i++) {
- generate_n(back_inserter(long_serial), serialno.size(), generator);
- long_serial.append("|");
+ generate_n(back_inserter(long_serialno), serialno.size(), generator);
+ long_serialno.append("|");
}
// String with the matching serialno should pass the verification.
- VerifyAbUpdateBinaryCommand(long_serial);
+ VerifyAbUpdateCommands(long_serialno);
}
-#endif // AB_OTA_UPDATER
diff --git a/tests/component/resources_test.cpp b/tests/component/resources_test.cpp
new file mode 100644
index 000000000..54329db22
--- /dev/null
+++ b/tests/component/resources_test.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+#include <png.h>
+
+#include "minui/minui.h"
+#include "private/resources.h"
+
+static const std::string kLocale = "zu";
+
+static const std::vector<std::string> kResourceImagesDirs{
+ "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;
+}
+
+// Finds out all the PNG files to test, which stay under the same dir with the executabl..
+static std::vector<std::string> add_files() {
+ std::vector<std::string> files;
+ for (const std::string& images_dir : kResourceImagesDirs) {
+ static std::string exec_dir = android::base::GetExecutableDirectory();
+ std::string dir_path = exec_dir + "/" + images_dir;
+ dirent** namelist;
+ int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort);
+ if (n == -1) {
+ printf("Failed to scandir %s: %s\n", dir_path.c_str(), strerror(errno));
+ continue;
+ }
+ if (n == 0) {
+ printf("No file is added for test in %s\n", dir_path.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 ResourcesTest : public testing::TestWithParam<std::string> {
+ public:
+ static std::vector<std::string> png_list;
+
+ protected:
+ void SetUp() override {
+ png_ = std::make_unique<PngHandler>(GetParam());
+ ASSERT_TRUE(png_);
+
+ ASSERT_EQ(PNG_COLOR_TYPE_GRAY, png_->color_type()) << "Recovery expects grayscale PNG file.";
+ ASSERT_LT(static_cast<png_uint_32>(5), png_->width());
+ ASSERT_LT(static_cast<png_uint_32>(0), png_->height());
+ ASSERT_EQ(1, png_->channels()) << "Recovery background text images expects 1-channel PNG file.";
+ }
+
+ std::unique_ptr<PngHandler> png_{ nullptr };
+};
+
+// Parses a png file and tests if it's qualified for the background text image under recovery.
+TEST_P(ResourcesTest, ValidateLocale) {
+ std::vector<unsigned char> row(png_->width());
+ for (png_uint_32 y = 0; y < png_->height(); ++y) {
+ png_read_row(png_->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(png_->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));
+ break;
+ }
+ for (int i = 0; i < h; ++i, ++y) {
+ png_read_row(png_->png_ptr(), row.data(), nullptr);
+ }
+ }
+}
+
+std::vector<std::string> ResourcesTest::png_list = add_files();
+
+INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourcesTest,
+ ::testing::ValuesIn(ResourcesTest::png_list.cbegin(),
+ ResourcesTest::png_list.cend()));
diff --git a/tests/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp
index 1544bb2a4..2420c27fe 100644
--- a/tests/component/update_verifier_test.cpp
+++ b/tests/component/update_verifier_test.cpp
@@ -14,29 +14,89 @@
* limitations under the License.
*/
+#include <update_verifier/update_verifier.h>
+
+#include <functional>
#include <string>
+#include <unordered_map>
+#include <vector>
#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
#include <android-base/test_utils.h>
+#include <google/protobuf/repeated_field.h>
#include <gtest/gtest.h>
-#include <update_verifier/update_verifier.h>
+
+#include "care_map.pb.h"
+
+using namespace std::string_literals;
class UpdateVerifierTest : public ::testing::Test {
protected:
void SetUp() override {
-#if defined(PRODUCT_SUPPORTS_VERITY) || defined(BOARD_AVB_ENABLE)
- verity_supported = true;
-#else
- verity_supported = false;
-#endif
+ std::string verity_mode = android::base::GetProperty("ro.boot.veritymode", "");
+ verity_supported = android::base::EqualsIgnoreCase(verity_mode, "enforcing");
+
+ care_map_prefix_ = care_map_dir_.path + "/care_map"s;
+ care_map_pb_ = care_map_dir_.path + "/care_map.pb"s;
+ care_map_txt_ = care_map_dir_.path + "/care_map.txt"s;
+ // Overrides the the care_map_prefix.
+ verifier_.set_care_map_prefix(care_map_prefix_);
+
+ property_id_ = "ro.build.fingerprint";
+ fingerprint_ = android::base::GetProperty(property_id_, "");
+ // Overrides the property_reader if we cannot read the given property on the device.
+ if (fingerprint_.empty()) {
+ fingerprint_ = "mock_fingerprint";
+ verifier_.set_property_reader([](const std::string& /* id */) { return "mock_fingerprint"; });
+ }
+ }
+
+ void TearDown() override {
+ unlink(care_map_pb_.c_str());
+ unlink(care_map_txt_.c_str());
+ }
+
+ // Returns a serialized string of the proto3 message according to the given partition info.
+ std::string ConstructProto(
+ std::vector<std::unordered_map<std::string, std::string>>& partitions) {
+ recovery_update_verifier::CareMap result;
+ for (const auto& partition : partitions) {
+ recovery_update_verifier::CareMap::PartitionInfo info;
+ if (partition.find("name") != partition.end()) {
+ info.set_name(partition.at("name"));
+ }
+ if (partition.find("ranges") != partition.end()) {
+ info.set_ranges(partition.at("ranges"));
+ }
+ if (partition.find("id") != partition.end()) {
+ info.set_id(partition.at("id"));
+ }
+ if (partition.find("fingerprint") != partition.end()) {
+ info.set_fingerprint(partition.at("fingerprint"));
+ }
+
+ *result.add_partitions() = info;
+ }
+
+ return result.SerializeAsString();
}
bool verity_supported;
+ UpdateVerifier verifier_;
+
+ TemporaryDir care_map_dir_;
+ std::string care_map_prefix_;
+ std::string care_map_pb_;
+ std::string care_map_txt_;
+
+ std::string property_id_;
+ std::string fingerprint_;
};
TEST_F(UpdateVerifierTest, verify_image_no_care_map) {
- // Non-existing care_map is allowed.
- ASSERT_TRUE(verify_image("/doesntexist"));
+ ASSERT_FALSE(verifier_.ParseCareMap());
}
TEST_F(UpdateVerifierTest, verify_image_smoke) {
@@ -46,26 +106,28 @@ TEST_F(UpdateVerifierTest, verify_image_smoke) {
return;
}
- TemporaryFile temp_file;
std::string content = "system\n2,0,1";
- ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
- ASSERT_TRUE(verify_image(temp_file.path));
+ ASSERT_TRUE(android::base::WriteStringToFile(content, care_map_txt_));
+ ASSERT_TRUE(verifier_.ParseCareMap());
+ ASSERT_TRUE(verifier_.VerifyPartitions());
// Leading and trailing newlines should be accepted.
- ASSERT_TRUE(android::base::WriteStringToFile("\n" + content + "\n\n", temp_file.path));
- ASSERT_TRUE(verify_image(temp_file.path));
+ ASSERT_TRUE(android::base::WriteStringToFile("\n" + content + "\n\n", care_map_txt_));
+ ASSERT_TRUE(verifier_.ParseCareMap());
+ ASSERT_TRUE(verifier_.VerifyPartitions());
+}
+
+TEST_F(UpdateVerifierTest, verify_image_empty_care_map) {
+ ASSERT_FALSE(verifier_.ParseCareMap());
}
TEST_F(UpdateVerifierTest, verify_image_wrong_lines) {
// The care map file can have only 2 / 4 / 6 lines.
- TemporaryFile temp_file;
- ASSERT_FALSE(verify_image(temp_file.path));
-
- ASSERT_TRUE(android::base::WriteStringToFile("line1", temp_file.path));
- ASSERT_FALSE(verify_image(temp_file.path));
+ ASSERT_TRUE(android::base::WriteStringToFile("line1", care_map_txt_));
+ ASSERT_FALSE(verifier_.ParseCareMap());
- ASSERT_TRUE(android::base::WriteStringToFile("line1\nline2\nline3", temp_file.path));
- ASSERT_FALSE(verify_image(temp_file.path));
+ ASSERT_TRUE(android::base::WriteStringToFile("line1\nline2\nline3", care_map_txt_));
+ ASSERT_FALSE(verifier_.ParseCareMap());
}
TEST_F(UpdateVerifierTest, verify_image_malformed_care_map) {
@@ -75,10 +137,9 @@ TEST_F(UpdateVerifierTest, verify_image_malformed_care_map) {
return;
}
- TemporaryFile temp_file;
std::string content = "system\n2,1,0";
- ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
- ASSERT_FALSE(verify_image(temp_file.path));
+ ASSERT_TRUE(android::base::WriteStringToFile(content, care_map_txt_));
+ ASSERT_FALSE(verifier_.ParseCareMap());
}
TEST_F(UpdateVerifierTest, verify_image_legacy_care_map) {
@@ -88,8 +149,110 @@ TEST_F(UpdateVerifierTest, verify_image_legacy_care_map) {
return;
}
- TemporaryFile temp_file;
std::string content = "/dev/block/bootdevice/by-name/system\n2,1,0";
- ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
- ASSERT_TRUE(verify_image(temp_file.path));
+ ASSERT_TRUE(android::base::WriteStringToFile(content, care_map_txt_));
+ ASSERT_FALSE(verifier_.ParseCareMap());
+}
+
+TEST_F(UpdateVerifierTest, verify_image_protobuf_care_map_smoke) {
+ // This test relies on dm-verity support.
+ if (!verity_supported) {
+ GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+ return;
+ }
+
+ std::vector<std::unordered_map<std::string, std::string>> partitions = {
+ {
+ { "name", "system" },
+ { "ranges", "2,0,1" },
+ { "id", property_id_ },
+ { "fingerprint", fingerprint_ },
+ },
+ };
+
+ std::string proto = ConstructProto(partitions);
+ ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_));
+ ASSERT_TRUE(verifier_.ParseCareMap());
+ ASSERT_TRUE(verifier_.VerifyPartitions());
+}
+
+TEST_F(UpdateVerifierTest, verify_image_protobuf_care_map_missing_name) {
+ // This test relies on dm-verity support.
+ if (!verity_supported) {
+ GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+ return;
+ }
+
+ std::vector<std::unordered_map<std::string, std::string>> partitions = {
+ {
+ { "ranges", "2,0,1" },
+ { "id", property_id_ },
+ { "fingerprint", fingerprint_ },
+ },
+ };
+
+ std::string proto = ConstructProto(partitions);
+ ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_));
+ ASSERT_FALSE(verifier_.ParseCareMap());
+}
+
+TEST_F(UpdateVerifierTest, verify_image_protobuf_care_map_bad_ranges) {
+ // This test relies on dm-verity support.
+ if (!verity_supported) {
+ GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+ return;
+ }
+
+ std::vector<std::unordered_map<std::string, std::string>> partitions = {
+ {
+ { "name", "system" },
+ { "ranges", "3,0,1" },
+ { "id", property_id_ },
+ { "fingerprint", fingerprint_ },
+ },
+ };
+
+ std::string proto = ConstructProto(partitions);
+ ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_));
+ ASSERT_FALSE(verifier_.ParseCareMap());
+}
+
+TEST_F(UpdateVerifierTest, verify_image_protobuf_empty_fingerprint) {
+ // This test relies on dm-verity support.
+ if (!verity_supported) {
+ GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+ return;
+ }
+
+ std::vector<std::unordered_map<std::string, std::string>> partitions = {
+ {
+ { "name", "system" },
+ { "ranges", "2,0,1" },
+ },
+ };
+
+ std::string proto = ConstructProto(partitions);
+ ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_));
+ ASSERT_FALSE(verifier_.ParseCareMap());
+}
+
+TEST_F(UpdateVerifierTest, verify_image_protobuf_fingerprint_mismatch) {
+ // This test relies on dm-verity support.
+ if (!verity_supported) {
+ GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+ return;
+ }
+
+ std::vector<std::unordered_map<std::string, std::string>> partitions = {
+ {
+ { "name", "system" },
+ { "ranges", "2,0,1" },
+ { "id", property_id_ },
+ { "fingerprint", "unsupported_fingerprint" },
+ },
+ };
+
+ std::string proto = ConstructProto(partitions);
+ ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_));
+ ASSERT_FALSE(verifier_.ParseCareMap());
}
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
index 5bfd7cb40..24c63e776 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/component/updater_test.cpp
@@ -27,6 +27,8 @@
#include <vector>
#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
@@ -35,26 +37,33 @@
#include <brotli/encode.h>
#include <bsdiff/bsdiff.h>
#include <gtest/gtest.h>
+#include <verity/hash_tree_builder.h>
#include <ziparchive/zip_archive.h>
#include <ziparchive/zip_writer.h>
+#include "applypatch/applypatch.h"
#include "common/test_constants.h"
#include "edify/expr.h"
-#include "otautil/SysUtil.h"
-#include "otautil/cache_location.h"
#include "otautil/error_code.h"
+#include "otautil/paths.h"
#include "otautil/print_sha1.h"
+#include "otautil/sysutil.h"
+#include "private/commands.h"
#include "updater/blockimg.h"
#include "updater/install.h"
#include "updater/updater.h"
-struct selabel_handle *sehandle = nullptr;
+using namespace std::string_literals;
-static void expect(const char* expected, const char* expr_str, CauseCode cause_code,
+using PackageEntries = std::unordered_map<std::string, std::string>;
+
+struct selabel_handle* sehandle = nullptr;
+
+static void expect(const char* expected, const std::string& 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, ParseString(expr_str, &e, &error_count));
ASSERT_EQ(0, error_count);
State state(expr_str, info);
@@ -65,7 +74,7 @@ static void expect(const char* expected, const char* expr_str, CauseCode cause_c
if (expected == nullptr) {
ASSERT_FALSE(status);
} else {
- ASSERT_TRUE(status);
+ ASSERT_TRUE(status) << "Evaluate() finished with error message: " << state.errmsg;
ASSERT_STREQ(expected, result.c_str());
}
@@ -76,12 +85,12 @@ static void expect(const char* expected, const char* expr_str, CauseCode cause_c
ASSERT_EQ(cause_code, state.cause_code);
}
-static void BuildUpdatePackage(const std::unordered_map<std::string, std::string>& entries,
- int fd) {
+static void BuildUpdatePackage(const PackageEntries& entries, int fd) {
FILE* zip_file_ptr = fdopen(fd, "wb");
ZipWriter zip_writer(zip_file_ptr);
for (const auto& entry : entries) {
+ // All the entries are written as STORED.
ASSERT_EQ(0, zip_writer.StartEntry(entry.first.c_str(), 0));
if (!entry.second.empty()) {
ASSERT_EQ(0, zip_writer.WriteBytes(entry.second.data(), entry.second.size()));
@@ -93,28 +102,103 @@ static void BuildUpdatePackage(const std::unordered_map<std::string, std::string
ASSERT_EQ(0, fclose(zip_file_ptr));
}
+static void RunBlockImageUpdate(bool is_verify, const PackageEntries& entries,
+ const std::string& image_file, const std::string& result,
+ CauseCode cause_code = kNoCause) {
+ CHECK(entries.find("transfer_list") != entries.end());
+
+ // Build the update package.
+ TemporaryFile zip_file;
+ BuildUpdatePackage(entries, zip_file.release());
+
+ MemMapping map;
+ ASSERT_TRUE(map.MapFile(zip_file.path));
+ 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 = fdopen(temp_pipe.release(), "wbe");
+ updater_info.package_zip_addr = map.addr;
+ updater_info.package_zip_len = map.length;
+
+ std::string new_data = entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data";
+ std::string script = is_verify ? "block_image_verify" : "block_image_update";
+ script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data +
+ R"(", "patch_data"))";
+ expect(result.c_str(), script, cause_code, &updater_info);
+
+ ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+ CloseArchive(handle);
+}
+
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);
}
+static Value* BlobToString(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ if (argv.size() != 1) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
+ }
+
+ std::vector<std::unique_ptr<Value>> args;
+ if (!ReadValueArgs(state, argv, &args)) {
+ return nullptr;
+ }
+
+ if (args[0]->type != Value::Type::BLOB) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() expects a BLOB argument", name);
+ }
+
+ args[0]->type = Value::Type::STRING;
+ return args[0].release();
+}
+
class UpdaterTest : public ::testing::Test {
protected:
- virtual void SetUp() override {
+ void SetUp() override {
RegisterBuiltins();
RegisterInstallFunctions();
RegisterBlockImageFunctions();
- // Mock the location of last_command_file.
- CacheLocation::location().set_cache_temp_source(temp_saved_source_.path);
- CacheLocation::location().set_last_command_file(temp_last_command_.path);
- CacheLocation::location().set_stash_directory_base(temp_stash_base_.path);
+ RegisterFunction("blob_to_string", BlobToString);
+
+ // Each test is run in a separate process (isolated mode). Shared temporary files won't cause
+ // conflicts.
+ Paths::Get().set_cache_temp_source(temp_saved_source_.path);
+ Paths::Get().set_last_command_file(temp_last_command_.path);
+ Paths::Get().set_stash_directory_base(temp_stash_base_.path);
+
+ // Enable a special command "abort" to simulate interruption.
+ Command::abort_allowed_ = true;
+
+ last_command_file_ = temp_last_command_.path;
+ image_file_ = image_temp_file_.path;
+ }
+
+ void TearDown() override {
+ // Clean up the last_command_file if any.
+ ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_));
+
+ // Clear partition updated marker if any.
+ std::string updated_marker{ temp_stash_base_.path };
+ updated_marker += "/" + get_sha1(image_temp_file_.path) + ".UPDATED";
+ ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
}
TemporaryFile temp_saved_source_;
- TemporaryFile temp_last_command_;
TemporaryDir temp_stash_base_;
+ std::string last_command_file_;
+ std::string image_file_;
+
+ private:
+ TemporaryFile temp_last_command_;
+ TemporaryFile image_temp_file_;
};
TEST_F(UpdaterTest, getprop) {
@@ -131,80 +215,47 @@ TEST_F(UpdaterTest, getprop) {
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, patch_partition_check) {
+ // Zero argument is not valid.
+ expect(nullptr, "patch_partition_check()", kArgsParsingFailure);
+
+ std::string source_file = from_testdata_base("boot.img");
+ std::string source_content;
+ ASSERT_TRUE(android::base::ReadFileToString(source_file, &source_content));
+ size_t source_size = source_content.size();
+ std::string source_hash = get_sha1(source_content);
+ Partition source(source_file, source_size, source_hash);
+
+ std::string target_file = from_testdata_base("recovery.img");
+ std::string target_content;
+ ASSERT_TRUE(android::base::ReadFileToString(target_file, &target_content));
+ size_t target_size = target_content.size();
+ std::string target_hash = get_sha1(target_content);
+ Partition target(target_file, target_size, target_hash);
+
+ // One argument is not valid.
+ expect(nullptr, "patch_partition_check(\"" + source.ToString() + "\")", kArgsParsingFailure);
+ expect(nullptr, "patch_partition_check(\"" + target.ToString() + "\")", kArgsParsingFailure);
+
+ // Both of the source and target have the desired checksum.
+ std::string cmd =
+ "patch_partition_check(\"" + source.ToString() + "\", \"" + target.ToString() + "\")";
+ expect("t", cmd, kNoCause);
+
+ // Only source partition has the desired checksum.
+ Partition bad_target(target_file, target_size - 1, target_hash);
+ cmd = "patch_partition_check(\"" + source.ToString() + "\", \"" + bad_target.ToString() + "\")";
+ expect("t", cmd, kNoCause);
+
+ // Only target partition has the desired checksum.
+ Partition bad_source(source_file, source_size + 1, source_hash);
+ cmd = "patch_partition_check(\"" + bad_source.ToString() + "\", \"" + target.ToString() + "\")";
+ expect("t", cmd, kNoCause);
+
+ // Neither of the source or target has the desired checksum.
+ cmd =
+ "patch_partition_check(\"" + bad_source.ToString() + "\", \"" + bad_target.ToString() + "\")";
+ expect("", cmd, kNoCause);
}
TEST_F(UpdaterTest, file_getprop) {
@@ -214,7 +265,7 @@ TEST_F(UpdaterTest, file_getprop) {
expect(nullptr, "file_getprop(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
// File doesn't exist.
- expect(nullptr, "file_getprop(\"/doesntexist\", \"key1\")", kFileGetPropFailure);
+ expect(nullptr, "file_getprop(\"/doesntexist\", \"key1\")", kFreadFailure);
// Reject too large files (current limit = 65536).
TemporaryFile temp_file1;
@@ -231,28 +282,28 @@ TEST_F(UpdaterTest, file_getprop) {
std::string script1("file_getprop(\"" + std::string(temp_file2.path) +
"\", \"ro.product.name\")");
- expect("tardis", script1.c_str(), kNoCause);
+ expect("tardis", script1, kNoCause);
std::string script2("file_getprop(\"" + std::string(temp_file2.path) +
"\", \"ro.product.board\")");
- expect("magic", script2.c_str(), kNoCause);
+ expect("magic", script2, kNoCause);
// No match.
std::string script3("file_getprop(\"" + std::string(temp_file2.path) +
"\", \"ro.product.wrong\")");
- expect("", script3.c_str(), kNoCause);
+ expect("", script3, kNoCause);
std::string script4("file_getprop(\"" + std::string(temp_file2.path) +
"\", \"ro.product.name=\")");
- expect("", script4.c_str(), kNoCause);
+ expect("", script4, kNoCause);
std::string script5("file_getprop(\"" + std::string(temp_file2.path) +
"\", \"ro.product.nam\")");
- expect("", script5.c_str(), kNoCause);
+ expect("", script5, kNoCause);
std::string script6("file_getprop(\"" + std::string(temp_file2.path) +
"\", \"ro.product.model\")");
- expect("", script6.c_str(), kNoCause);
+ expect("", script6, kNoCause);
}
// TODO: Test extracting to block device.
@@ -272,7 +323,7 @@ TEST_F(UpdaterTest, package_extract_file) {
// 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);
+ expect("t", script, kNoCause, &updater_info);
// Verify the extracted entry.
std::string data;
@@ -281,33 +332,135 @@ TEST_F(UpdaterTest, package_extract_file) {
// 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);
+ expect("t", script, 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);
+ expect("", script, kNoCause, &updater_info);
// Extract to /dev/full should fail.
script = "package_extract_file(\"a.txt\", \"/dev/full\")";
- expect("", script.c_str(), kNoCause, &updater_info);
+ expect("", script, kNoCause, &updater_info);
- // One-argument version.
- script = "sha1_check(package_extract_file(\"a.txt\"))";
- expect(kATxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info);
+ // One-argument version. package_extract_file() gives a VAL_BLOB, which needs to be converted to
+ // VAL_STRING for equality test.
+ script = "blob_to_string(package_extract_file(\"a.txt\")) == \"" + kATxtContents + "\"";
+ expect("t", script, kNoCause, &updater_info);
- script = "sha1_check(package_extract_file(\"b.txt\"))";
- expect(kBTxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info);
+ script = "blob_to_string(package_extract_file(\"b.txt\")) == \"" + kBTxtContents + "\"";
+ expect("t", script, 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);
+ expect(nullptr, script, kPackageExtractFileFailure, &updater_info);
CloseArchive(handle);
}
+TEST_F(UpdaterTest, read_file) {
+ // read_file() expects one argument.
+ expect(nullptr, "read_file()", kArgsParsingFailure);
+ expect(nullptr, "read_file(\"arg1\", \"arg2\")", kArgsParsingFailure);
+
+ // Write some value to file and read back.
+ TemporaryFile temp_file;
+ std::string script("write_value(\"foo\", \""s + temp_file.path + "\");");
+ expect("t", script, kNoCause);
+
+ script = "read_file(\""s + temp_file.path + "\") == \"foo\"";
+ expect("t", script, kNoCause);
+
+ script = "read_file(\""s + temp_file.path + "\") == \"bar\"";
+ expect("", script, kNoCause);
+
+ // It should fail gracefully when read fails.
+ script = "read_file(\"/doesntexist\")";
+ expect("", script, kNoCause);
+}
+
+TEST_F(UpdaterTest, compute_hash_tree_smoke) {
+ std::string data;
+ for (unsigned char i = 0; i < 128; i++) {
+ data += std::string(4096, i);
+ }
+ // Appends an additional block for verity data.
+ data += std::string(4096, 0);
+ ASSERT_EQ(129 * 4096, data.size());
+ ASSERT_TRUE(android::base::WriteStringToFile(data, image_file_));
+
+ std::string salt = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7";
+ std::string expected_root_hash =
+ "7e0a8d8747f54384014ab996f5b2dc4eb7ff00c630eede7134c9e3f05c0dd8ca";
+ // hash_tree_ranges, source_ranges, hash_algorithm, salt_hex, root_hash
+ std::vector<std::string> tokens{ "compute_hash_tree", "2,128,129", "2,0,128", "sha256", salt,
+ expected_root_hash };
+ std::string hash_tree_command = android::base::Join(tokens, " ");
+
+ std::vector<std::string> transfer_list{
+ "4", "2", "0", "2", hash_tree_command,
+ };
+
+ PackageEntries entries{
+ { "new_data", "" },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, "\n") },
+ };
+
+ RunBlockImageUpdate(false, entries, image_file_, "t");
+
+ std::string updated;
+ ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated));
+ ASSERT_EQ(129 * 4096, updated.size());
+ ASSERT_EQ(data.substr(0, 128 * 4096), updated.substr(0, 128 * 4096));
+
+ // Computes the SHA256 of the salt + hash_tree_data and expects the result to match with the
+ // root_hash.
+ std::vector<unsigned char> salt_bytes;
+ ASSERT_TRUE(HashTreeBuilder::ParseBytesArrayFromString(salt, &salt_bytes));
+ std::vector<unsigned char> hash_tree = std::move(salt_bytes);
+ hash_tree.insert(hash_tree.end(), updated.begin() + 128 * 4096, updated.end());
+
+ std::vector<unsigned char> digest(SHA256_DIGEST_LENGTH);
+ SHA256(hash_tree.data(), hash_tree.size(), digest.data());
+ ASSERT_EQ(expected_root_hash, HashTreeBuilder::BytesArrayToString(digest));
+}
+
+TEST_F(UpdaterTest, compute_hash_tree_root_mismatch) {
+ std::string data;
+ for (size_t i = 0; i < 128; i++) {
+ data += std::string(4096, i);
+ }
+ // Appends an additional block for verity data.
+ data += std::string(4096, 0);
+ ASSERT_EQ(129 * 4096, data.size());
+ // Corrupts one bit
+ data[4096] = 'A';
+ ASSERT_TRUE(android::base::WriteStringToFile(data, image_file_));
+
+ std::string salt = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7";
+ std::string expected_root_hash =
+ "7e0a8d8747f54384014ab996f5b2dc4eb7ff00c630eede7134c9e3f05c0dd8ca";
+ // hash_tree_ranges, source_ranges, hash_algorithm, salt_hex, root_hash
+ std::vector<std::string> tokens{ "compute_hash_tree", "2,128,129", "2,0,128", "sha256", salt,
+ expected_root_hash };
+ std::string hash_tree_command = android::base::Join(tokens, " ");
+
+ std::vector<std::string> transfer_list{
+ "4", "2", "0", "2", hash_tree_command,
+ };
+
+ PackageEntries entries{
+ { "new_data", "" },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, "\n") },
+ };
+
+ RunBlockImageUpdate(false, entries, image_file_, "", kHashTreeComputationFailure);
+}
+
TEST_F(UpdaterTest, write_value) {
// write_value() expects two arguments.
expect(nullptr, "write_value()", kArgsParsingFailure);
@@ -321,7 +474,7 @@ TEST_F(UpdaterTest, write_value) {
TemporaryFile temp_file;
std::string value = "magicvalue";
std::string script("write_value(\"" + value + "\", \"" + std::string(temp_file.path) + "\")");
- expect("t", script.c_str(), kNoCause);
+ expect("t", script, kNoCause);
// Verify the content.
std::string content;
@@ -330,7 +483,7 @@ TEST_F(UpdaterTest, write_value) {
// Allow writing empty string.
script = "write_value(\"\", \"" + std::string(temp_file.path) + "\")";
- expect("t", script.c_str(), kNoCause);
+ expect("t", script, kNoCause);
// Verify the content.
ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content));
@@ -338,7 +491,7 @@ TEST_F(UpdaterTest, write_value) {
// It should fail gracefully when write fails.
script = "write_value(\"value\", \"/proc/0/file1\")";
- expect("", script.c_str(), kNoCause);
+ expect("", script, kNoCause);
}
TEST_F(UpdaterTest, get_stage) {
@@ -357,11 +510,11 @@ TEST_F(UpdaterTest, get_stage) {
// Can read the stage value.
std::string script("get_stage(\"" + temp_file + "\")");
- expect("2/3", script.c_str(), kNoCause);
+ expect("2/3", script, kNoCause);
// Bad BCB path.
script = "get_stage(\"doesntexist\")";
- expect("", script.c_str(), kNoCause);
+ expect("", script, kNoCause);
}
TEST_F(UpdaterTest, set_stage) {
@@ -381,7 +534,7 @@ TEST_F(UpdaterTest, set_stage) {
// Write with set_stage().
std::string script("set_stage(\"" + temp_file + "\", \"1/3\")");
- expect(tf.path, script.c_str(), kNoCause);
+ expect(tf.path, script, kNoCause);
// Verify.
bootloader_message boot_verify;
@@ -393,10 +546,10 @@ TEST_F(UpdaterTest, set_stage) {
// Bad BCB path.
script = "set_stage(\"doesntexist\", \"1/3\")";
- expect("", script.c_str(), kNoCause);
+ expect("", script, kNoCause);
script = "set_stage(\"/dev/full\", \"1/3\")";
- expect("", script.c_str(), kNoCause);
+ expect("", script, kNoCause);
}
TEST_F(UpdaterTest, set_progress) {
@@ -448,22 +601,42 @@ TEST_F(UpdaterTest, show_progress) {
ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
}
+TEST_F(UpdaterTest, block_image_update_parsing_error) {
+ std::vector<std::string> transfer_list{
+ // clang-format off
+ "4",
+ "2",
+ "0",
+ // clang-format on
+ };
+
+ PackageEntries entries{
+ { "new_data", "" },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, '\n') },
+ };
+
+ RunBlockImageUpdate(false, entries, image_file_, "", kArgsParsingFailure);
+}
+
TEST_F(UpdaterTest, block_image_update_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');
// Generate the patch data.
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));
+ 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));
// Create the transfer list that contains a bsdiff.
std::string src_hash = get_sha1(src_content);
std::string tgt_hash = get_sha1(tgt_content);
- std::vector<std::string> transfer_list = {
+ std::vector<std::string> transfer_list{
+ // clang-format off
"4",
"2",
"0",
@@ -472,183 +645,108 @@ TEST_F(UpdaterTest, block_image_update_patch_data) {
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,
+ // clang-format on
};
- std::unordered_map<std::string, std::string> entries = {
+ PackageEntries entries{
{ "new_data", "" },
{ "patch_data", patch_content },
{ "transfer_list", android::base::Join(transfer_list, '\n') },
};
- // Build the update package.
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- ZipArchiveHandle handle;
- ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+ ASSERT_TRUE(android::base::WriteStringToFile(src_content, image_file_));
- // Set up the handler, command_pipe, patch offset & length.
- UpdaterInfo updater_info;
- updater_info.package_zip = handle;
- TemporaryFile temp_pipe;
- updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
+ RunBlockImageUpdate(false, entries, image_file_, "t");
- // Execute the commands in the 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));
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
+ ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_content));
+ ASSERT_EQ(tgt_content, updated_content);
}
TEST_F(UpdaterTest, block_image_update_fail) {
std::string src_content(4096 * 2, 'e');
std::string src_hash = get_sha1(src_content);
// Stash and free some blocks, then fail the update intentionally.
- std::vector<std::string> transfer_list = {
- "4", "2", "0", "2", "stash " + src_hash + " 2,0,2", "free " + src_hash, "fail",
+ std::vector<std::string> transfer_list{
+ // clang-format off
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + src_hash + " 2,0,2",
+ "free " + src_hash,
+ "abort",
+ // clang-format on
};
// Add a new data of 10 bytes to test the deadlock.
- std::unordered_map<std::string, std::string> entries = {
+ PackageEntries entries{
{ "new_data", std::string(10, 0) },
{ "patch_data", "" },
{ "transfer_list", android::base::Join(transfer_list, '\n') },
};
- // Build the update package.
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- ZipArchiveHandle handle;
- ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+ ASSERT_TRUE(android::base::WriteStringToFile(src_content, image_file_));
- // Set up the handler, command_pipe, patch offset & length.
- UpdaterInfo updater_info;
- updater_info.package_zip = handle;
- TemporaryFile temp_pipe;
- updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
+ RunBlockImageUpdate(false, entries, image_file_, "");
- TemporaryFile update_file;
- ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
- // Expect the stashed blocks to be freed.
- std::string script = "block_image_update(\"" + std::string(update_file.path) +
- R"(", package_extract_file("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 name_digest = get_sha1(image_file_);
std::string stash_base = std::string(temp_stash_base_.path) + "/" + name_digest;
ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
+ // Expect the stashed blocks to be freed.
ASSERT_EQ(-1, access((stash_base + src_hash).c_str(), F_OK));
ASSERT_EQ(0, rmdir(stash_base.c_str()));
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
}
TEST_F(UpdaterTest, new_data_over_write) {
- std::vector<std::string> transfer_list = {
- "4", "1", "0", "0", "new 2,0,1",
+ std::vector<std::string> transfer_list{
+ // clang-format off
+ "4",
+ "1",
+ "0",
+ "0",
+ "new 2,0,1",
+ // clang-format on
};
// Write 4096 + 100 bytes of new data.
- std::unordered_map<std::string, std::string> entries = {
+ PackageEntries entries{
{ "new_data", std::string(4196, 0) },
{ "patch_data", "" },
{ "transfer_list", android::base::Join(transfer_list, '\n') },
};
- // Build the update package.
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- 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 = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
-
- TemporaryFile update_file;
- 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);
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
+ RunBlockImageUpdate(false, entries, image_file_, "t");
}
TEST_F(UpdaterTest, new_data_short_write) {
- std::vector<std::string> transfer_list = {
+ std::vector<std::string> transfer_list{
+ // clang-format off
"4",
"1",
"0",
"0",
"new 2,0,1",
+ // clang-format on
};
- std::unordered_map<std::string, std::string> entries = {
- { "empty_new_data", "" },
- { "short_new_data", std::string(10, 'a') },
- { "exact_new_data", std::string(4096, 'a') },
+ PackageEntries entries{
{ "patch_data", "" },
{ "transfer_list", android::base::Join(transfer_list, '\n') },
};
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- 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 = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
-
// Updater should report the failure gracefully rather than stuck in deadlock.
- TemporaryFile update_file;
- std::string script_empty_data = "block_image_update(\"" + std::string(update_file.path) +
- R"(", package_extract_file("transfer_list"), "empty_new_data", "patch_data"))";
- expect("", script_empty_data.c_str(), kNoCause, &updater_info);
+ entries["new_data"] = "";
+ RunBlockImageUpdate(false, entries, image_file_, "");
- std::string script_short_data = "block_image_update(\"" + std::string(update_file.path) +
- R"(", package_extract_file("transfer_list"), "short_new_data", "patch_data"))";
- expect("", script_short_data.c_str(), kNoCause, &updater_info);
+ entries["new_data"] = std::string(10, 'a');
+ RunBlockImageUpdate(false, entries, image_file_, "");
// Expect to write 1 block of new data successfully.
- std::string script_exact_data = "block_image_update(\"" + std::string(update_file.path) +
- R"(", package_extract_file("transfer_list"), "exact_new_data", "patch_data"))";
- expect("t", script_exact_data.c_str(), kNoCause, &updater_info);
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
+ entries["new_data"] = std::string(4096, 'a');
+ RunBlockImageUpdate(false, entries, image_file_, "t");
}
TEST_F(UpdaterTest, brotli_new_data) {
@@ -681,55 +779,30 @@ TEST_F(UpdaterTest, brotli_new_data) {
"new 2,99,100",
};
- std::unordered_map<std::string, std::string> entries = {
- { "new.dat.br", std::move(encoded_data) },
+ PackageEntries entries{
+ { "new_data.br", std::move(encoded_data) },
{ "patch_data", "" },
{ "transfer_list", android::base::Join(transfer_list, '\n') },
};
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- 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 = fdopen(temp_pipe.release(), "wb");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
-
- // Check if we can decompress the new data correctly.
- TemporaryFile update_file;
- std::string script_new_data =
- "block_image_update(\"" + std::string(update_file.path) +
- R"(", package_extract_file("transfer_list"), "new.dat.br", "patch_data"))";
- expect("t", script_new_data.c_str(), kNoCause, &updater_info);
+ RunBlockImageUpdate(false, entries, image_file_, "t");
std::string updated_content;
- ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
+ ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_content));
ASSERT_EQ(brotli_new_data, updated_content);
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
}
TEST_F(UpdaterTest, last_command_update) {
- std::string last_command_file = CacheLocation::location().last_command_file();
-
- std::string block1 = std::string(4096, '1');
- std::string block2 = std::string(4096, '2');
- std::string block3 = std::string(4096, '3');
+ std::string block1(4096, '1');
+ std::string block2(4096, '2');
+ std::string block3(4096, '3');
std::string block1_hash = get_sha1(block1);
std::string block2_hash = get_sha1(block2);
std::string block3_hash = get_sha1(block3);
// Compose the transfer list to fail the first update.
- std::vector<std::string> transfer_list_fail = {
+ std::vector<std::string> transfer_list_fail{
+ // clang-format off
"4",
"2",
"0",
@@ -737,11 +810,13 @@ TEST_F(UpdaterTest, last_command_update) {
"stash " + block1_hash + " 2,0,1",
"move " + block1_hash + " 2,1,2 1 2,0,1",
"stash " + block3_hash + " 2,2,3",
- "fail",
+ "abort",
+ // clang-format on
};
// Mimic a resumed update with the same transfer commands.
- std::vector<std::string> transfer_list_continue = {
+ std::vector<std::string> transfer_list_continue{
+ // clang-format off
"4",
"2",
"0",
@@ -750,127 +825,88 @@ TEST_F(UpdaterTest, last_command_update) {
"move " + block1_hash + " 2,1,2 1 2,0,1",
"stash " + block3_hash + " 2,2,3",
"move " + block1_hash + " 2,2,3 1 2,0,1",
+ // clang-format on
};
- std::unordered_map<std::string, std::string> entries = {
+ ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_));
+
+ PackageEntries entries{
{ "new_data", "" },
{ "patch_data", "" },
- { "transfer_list_fail", android::base::Join(transfer_list_fail, '\n') },
- { "transfer_list_continue", android::base::Join(transfer_list_continue, '\n') },
+ { "transfer_list", android::base::Join(transfer_list_fail, '\n') },
};
- // Build the update package.
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- 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 = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
+ // "2\nstash " + block3_hash + " 2,2,3"
+ std::string last_command_content =
+ "2\n" + transfer_list_fail[TransferList::kTransferListHeaderLines + 2];
- std::string src_content = block1 + block2 + block3;
- 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_fail"), "new_data", "patch_data"))";
- expect("", script.c_str(), kNoCause, &updater_info);
+ RunBlockImageUpdate(false, entries, image_file_, "");
// Expect last_command to contain the last stash command.
- std::string last_command_content;
- ASSERT_TRUE(android::base::ReadFileToString(last_command_file.c_str(), &last_command_content));
- EXPECT_EQ("2\nstash " + block3_hash + " 2,2,3", last_command_content);
+ std::string last_command_actual;
+ ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual));
+ EXPECT_EQ(last_command_content, last_command_actual);
+
std::string updated_contents;
- ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_contents));
+ ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_contents));
ASSERT_EQ(block1 + block1 + block3, updated_contents);
- // Resume the update, expect the first 'move' to be skipped but the second 'move' to be executed.
- ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
- std::string script_second_update =
- "block_image_update(\"" + std::string(update_file.path) +
- R"(", package_extract_file("transfer_list_continue"), "new_data", "patch_data"))";
- expect("t", script_second_update.c_str(), kNoCause, &updater_info);
- ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_contents));
- ASSERT_EQ(block1 + block2 + block1, updated_contents);
+ // "Resume" the update. Expect the first 'move' to be skipped but the second 'move' to be
+ // executed. Note that we intentionally reset the image file.
+ entries["transfer_list"] = android::base::Join(transfer_list_continue, '\n');
+ ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_));
+ RunBlockImageUpdate(false, entries, image_file_, "t");
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
+ ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_contents));
+ ASSERT_EQ(block1 + block2 + block1, updated_contents);
}
TEST_F(UpdaterTest, last_command_update_unresumable) {
- std::string last_command_file = CacheLocation::location().last_command_file();
-
- std::string block1 = std::string(4096, '1');
- std::string block2 = std::string(4096, '2');
+ std::string block1(4096, '1');
+ std::string block2(4096, '2');
std::string block1_hash = get_sha1(block1);
std::string block2_hash = get_sha1(block2);
// Construct an unresumable update with source blocks mismatch.
- std::vector<std::string> transfer_list_unresumable = {
- "4", "2", "0", "2", "stash " + block1_hash + " 2,0,1", "move " + block2_hash + " 2,1,2 1 2,0,1",
+ std::vector<std::string> transfer_list_unresumable{
+ // clang-format off
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + block1_hash + " 2,0,1",
+ "move " + block2_hash + " 2,1,2 1 2,0,1",
+ // clang-format on
};
- std::unordered_map<std::string, std::string> entries = {
+ PackageEntries entries{
{ "new_data", "" },
{ "patch_data", "" },
- { "transfer_list_unresumable", android::base::Join(transfer_list_unresumable, '\n') },
+ { "transfer_list", android::base::Join(transfer_list_unresumable, '\n') },
};
- // Build the update package.
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- ZipArchiveHandle handle;
- ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+ ASSERT_TRUE(android::base::WriteStringToFile(block1 + block1, image_file_));
- // Set up the handler, command_pipe, patch offset & length.
- UpdaterInfo updater_info;
- updater_info.package_zip = handle;
- TemporaryFile temp_pipe;
- updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
+ std::string last_command_content =
+ "0\n" + transfer_list_unresumable[TransferList::kTransferListHeaderLines];
+ ASSERT_TRUE(android::base::WriteStringToFile(last_command_content, last_command_file_));
- // Set up the last_command_file
- ASSERT_TRUE(
- android::base::WriteStringToFile("0\nstash " + block1_hash + " 2,0,1", last_command_file));
-
- // The last_command_file will be deleted if the update encounters an unresumable failure
- // later.
- std::string src_content = block1 + block1;
- 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_unresumable"), "new_data", "patch_data"))";
- expect("", script.c_str(), kNoCause, &updater_info);
- ASSERT_EQ(-1, access(last_command_file.c_str(), R_OK));
+ RunBlockImageUpdate(false, entries, image_file_, "");
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
+ // The last_command_file will be deleted if the update encounters an unresumable failure later.
+ ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
}
TEST_F(UpdaterTest, last_command_verify) {
- std::string last_command_file = CacheLocation::location().last_command_file();
-
- std::string block1 = std::string(4096, '1');
- std::string block2 = std::string(4096, '2');
- std::string block3 = std::string(4096, '3');
+ std::string block1(4096, '1');
+ std::string block2(4096, '2');
+ std::string block3(4096, '3');
std::string block1_hash = get_sha1(block1);
std::string block2_hash = get_sha1(block2);
std::string block3_hash = get_sha1(block3);
- std::vector<std::string> transfer_list_verify = {
+ std::vector<std::string> transfer_list_verify{
+ // clang-format off
"4",
"2",
"0",
@@ -879,55 +915,265 @@ TEST_F(UpdaterTest, last_command_verify) {
"move " + block1_hash + " 2,0,1 1 2,0,1",
"move " + block1_hash + " 2,1,2 1 2,0,1",
"stash " + block3_hash + " 2,2,3",
+ // clang-format on
};
- std::unordered_map<std::string, std::string> entries = {
+ PackageEntries entries{
{ "new_data", "" },
{ "patch_data", "" },
- { "transfer_list_verify", android::base::Join(transfer_list_verify, '\n') },
+ { "transfer_list", android::base::Join(transfer_list_verify, '\n') },
};
- // Build the update package.
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
+ ASSERT_TRUE(android::base::WriteStringToFile(block1 + block1 + block3, image_file_));
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- ZipArchiveHandle handle;
- ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+ // Last command: "move " + block1_hash + " 2,1,2 1 2,0,1"
+ std::string last_command_content =
+ "2\n" + transfer_list_verify[TransferList::kTransferListHeaderLines + 2];
- // Set up the handler, command_pipe, patch offset & length.
- UpdaterInfo updater_info;
- updater_info.package_zip = handle;
- TemporaryFile temp_pipe;
- updater_info.cmd_pipe = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
+ // First run: expect the verification to succeed and the last_command_file is intact.
+ ASSERT_TRUE(android::base::WriteStringToFile(last_command_content, last_command_file_));
+
+ RunBlockImageUpdate(true, entries, image_file_, "t");
- std::string src_content = block1 + block1 + block3;
- TemporaryFile update_file;
- ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+ std::string last_command_actual;
+ ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual));
+ EXPECT_EQ(last_command_content, last_command_actual);
- ASSERT_TRUE(
- android::base::WriteStringToFile("2\nstash " + block3_hash + " 2,2,3", last_command_file));
+ // Second run with a mismatching block image: expect the verification to succeed but
+ // last_command_file to be deleted; because the target blocks in the last command don't have the
+ // expected contents for the second move command.
+ ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_));
+ RunBlockImageUpdate(true, entries, image_file_, "t");
+ ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
+}
- // Expect the verification to succeed and the last_command_file is intact.
- std::string script_verify =
- "block_image_verify(\"" + std::string(update_file.path) +
- R"(", package_extract_file("transfer_list_verify"), "new_data","patch_data"))";
- expect("t", script_verify.c_str(), kNoCause, &updater_info);
+class ResumableUpdaterTest : public testing::TestWithParam<size_t> {
+ protected:
+ void SetUp() override {
+ RegisterBuiltins();
+ RegisterInstallFunctions();
+ RegisterBlockImageFunctions();
- std::string last_command_content;
- ASSERT_TRUE(android::base::ReadFileToString(last_command_file.c_str(), &last_command_content));
- EXPECT_EQ("2\nstash " + block3_hash + " 2,2,3", last_command_content);
+ Paths::Get().set_cache_temp_source(temp_saved_source_.path);
+ Paths::Get().set_last_command_file(temp_last_command_.path);
+ Paths::Get().set_stash_directory_base(temp_stash_base_.path);
- // Expect the verification to succeed but last_command_file to be deleted; because the target
- // blocks don't have the expected contents for the second move command.
- src_content = block1 + block2 + block3;
- ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
- expect("t", script_verify.c_str(), kNoCause, &updater_info);
- ASSERT_EQ(-1, access(last_command_file.c_str(), R_OK));
+ // Enable a special command "abort" to simulate interruption.
+ Command::abort_allowed_ = true;
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
+ index_ = GetParam();
+ image_file_ = image_temp_file_.path;
+ last_command_file_ = temp_last_command_.path;
+ }
+
+ void TearDown() override {
+ // Clean up the last_command_file if any.
+ ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_));
+
+ // Clear partition updated marker if any.
+ std::string updated_marker{ temp_stash_base_.path };
+ updated_marker += "/" + get_sha1(image_temp_file_.path) + ".UPDATED";
+ ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
+ }
+
+ TemporaryFile temp_saved_source_;
+ TemporaryDir temp_stash_base_;
+ std::string last_command_file_;
+ std::string image_file_;
+ size_t index_;
+
+ private:
+ TemporaryFile temp_last_command_;
+ TemporaryFile image_temp_file_;
+};
+
+static std::string g_source_image;
+static std::string g_target_image;
+static PackageEntries g_entries;
+
+static std::vector<std::string> GenerateTransferList() {
+ std::string a(4096, 'a');
+ std::string b(4096, 'b');
+ std::string c(4096, 'c');
+ std::string d(4096, 'd');
+ std::string e(4096, 'e');
+ std::string f(4096, 'f');
+ std::string g(4096, 'g');
+ std::string h(4096, 'h');
+ std::string i(4096, 'i');
+ std::string zero(4096, '\0');
+
+ std::string a_hash = get_sha1(a);
+ std::string b_hash = get_sha1(b);
+ std::string c_hash = get_sha1(c);
+ std::string e_hash = get_sha1(e);
+
+ auto loc = [](const std::string& range_text) {
+ std::vector<std::string> pieces = android::base::Split(range_text, "-");
+ size_t left;
+ size_t right;
+ if (pieces.size() == 1) {
+ CHECK(android::base::ParseUint(pieces[0], &left));
+ right = left + 1;
+ } else {
+ CHECK_EQ(2u, pieces.size());
+ CHECK(android::base::ParseUint(pieces[0], &left));
+ CHECK(android::base::ParseUint(pieces[1], &right));
+ right++;
+ }
+ return android::base::StringPrintf("2,%zu,%zu", left, right);
+ };
+
+ // patch 1: "b d c" -> "g"
+ TemporaryFile patch_file_bdc_g;
+ std::string bdc = b + d + c;
+ std::string bdc_hash = get_sha1(bdc);
+ std::string g_hash = get_sha1(g);
+ CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(bdc.data()), bdc.size(),
+ reinterpret_cast<const uint8_t*>(g.data()), g.size(),
+ patch_file_bdc_g.path, nullptr));
+ std::string patch_bdc_g;
+ CHECK(android::base::ReadFileToString(patch_file_bdc_g.path, &patch_bdc_g));
+
+ // patch 2: "a b c d" -> "d c b"
+ TemporaryFile patch_file_abcd_dcb;
+ std::string abcd = a + b + c + d;
+ std::string abcd_hash = get_sha1(abcd);
+ std::string dcb = d + c + b;
+ std::string dcb_hash = get_sha1(dcb);
+ CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(abcd.data()), abcd.size(),
+ reinterpret_cast<const uint8_t*>(dcb.data()), dcb.size(),
+ patch_file_abcd_dcb.path, nullptr));
+ std::string patch_abcd_dcb;
+ CHECK(android::base::ReadFileToString(patch_file_abcd_dcb.path, &patch_abcd_dcb));
+
+ std::vector<std::string> transfer_list{
+ "4",
+ "10", // total blocks written
+ "2", // maximum stash entries
+ "2", // maximum number of stashed blocks
+
+ // a b c d e a b c d e
+ "stash " + b_hash + " " + loc("1"),
+ // a b c d e a b c d e [b(1)]
+ "stash " + c_hash + " " + loc("2"),
+ // a b c d e a b c d e [b(1)][c(2)]
+ "new " + loc("1-2"),
+ // a i h d e a b c d e [b(1)][c(2)]
+ "zero " + loc("0"),
+ // 0 i h d e a b c d e [b(1)][c(2)]
+
+ // bsdiff "b d c" (from stash, 3, stash) to get g(3)
+ android::base::StringPrintf(
+ "bsdiff 0 %zu %s %s %s 3 %s %s %s:%s %s:%s",
+ patch_bdc_g.size(), // patch start (0), patch length
+ bdc_hash.c_str(), // source hash
+ g_hash.c_str(), // target hash
+ loc("3").c_str(), // target range
+ loc("3").c_str(), loc("1").c_str(), // load "d" from block 3, into buffer at offset 1
+ b_hash.c_str(), loc("0").c_str(), // load "b" from stash, into buffer at offset 0
+ c_hash.c_str(), loc("2").c_str()), // load "c" from stash, into buffer at offset 2
+
+ // 0 i h g e a b c d e [b(1)][c(2)]
+ "free " + b_hash,
+ // 0 i h g e a b c d e [c(2)]
+ "free " + a_hash,
+ // 0 i h g e a b c d e
+ "stash " + a_hash + " " + loc("5"),
+ // 0 i h g e a b c d e [a(5)]
+ "move " + e_hash + " " + loc("5") + " 1 " + loc("4"),
+ // 0 i h g e e b c d e [a(5)]
+
+ // bsdiff "a b c d" (from stash, 6-8) to "d c b" (6-8)
+ android::base::StringPrintf( //
+ "bsdiff %zu %zu %s %s %s 4 %s %s %s:%s",
+ patch_bdc_g.size(), // patch start
+ patch_bdc_g.size() + patch_abcd_dcb.size(), // patch length
+ abcd_hash.c_str(), // source hash
+ dcb_hash.c_str(), // target hash
+ loc("6-8").c_str(), // target range
+ loc("6-8").c_str(), // load "b c d" from blocks 6-8
+ loc("1-3").c_str(), // into buffer at offset 1-3
+ a_hash.c_str(), // load "a" from stash
+ loc("0").c_str()), // into buffer at offset 0
+
+ // 0 i h g e e d c b e [a(5)]
+ "new " + loc("4"),
+ // 0 i h g f e d c b e [a(5)]
+ "move " + a_hash + " " + loc("9") + " 1 - " + a_hash + ":" + loc("0"),
+ // 0 i h g f e d c b a [a(5)]
+ "free " + a_hash,
+ // 0 i h g f e d c b a
+ };
+
+ std::string new_data = i + h + f;
+ std::string patch_data = patch_bdc_g + patch_abcd_dcb;
+
+ g_entries = {
+ { "new_data", new_data },
+ { "patch_data", patch_data },
+ };
+ g_source_image = a + b + c + d + e + a + b + c + d + e;
+ g_target_image = zero + i + h + g + f + e + d + c + b + a;
+
+ return transfer_list;
+}
+
+static const std::vector<std::string> g_transfer_list = GenerateTransferList();
+
+INSTANTIATE_TEST_CASE_P(InterruptAfterEachCommand, ResumableUpdaterTest,
+ ::testing::Range(static_cast<size_t>(0),
+ g_transfer_list.size() -
+ TransferList::kTransferListHeaderLines));
+
+TEST_P(ResumableUpdaterTest, InterruptVerifyResume) {
+ ASSERT_TRUE(android::base::WriteStringToFile(g_source_image, image_file_));
+
+ LOG(INFO) << "Interrupting at line " << index_ << " ("
+ << g_transfer_list[TransferList::kTransferListHeaderLines + index_] << ")";
+
+ std::vector<std::string> transfer_list_copy{ g_transfer_list };
+ transfer_list_copy[TransferList::kTransferListHeaderLines + index_] = "abort";
+
+ g_entries["transfer_list"] = android::base::Join(transfer_list_copy, '\n');
+
+ // Run update that's expected to fail.
+ RunBlockImageUpdate(false, g_entries, image_file_, "");
+
+ std::string last_command_expected;
+
+ // Assert the last_command_file.
+ if (index_ == 0) {
+ ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
+ } else {
+ last_command_expected = std::to_string(index_ - 1) + "\n" +
+ g_transfer_list[TransferList::kTransferListHeaderLines + index_ - 1];
+ std::string last_command_actual;
+ ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual));
+ ASSERT_EQ(last_command_expected, last_command_actual);
+ }
+
+ g_entries["transfer_list"] = android::base::Join(g_transfer_list, '\n');
+
+ // Resume the interrupted update, by doing verification first.
+ RunBlockImageUpdate(true, g_entries, image_file_, "t");
+
+ // last_command_file should remain intact.
+ if (index_ == 0) {
+ ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
+ } else {
+ std::string last_command_actual;
+ ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual));
+ ASSERT_EQ(last_command_expected, last_command_actual);
+ }
+
+ // Resume the update.
+ RunBlockImageUpdate(false, g_entries, image_file_, "t");
+
+ // last_command_file should be gone after successful update.
+ ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
+
+ std::string updated_image_actual;
+ ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_image_actual));
+ ASSERT_EQ(g_target_image, updated_image_actual);
}
diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp
index 2ef3828ad..3246ecdbc 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -16,7 +16,6 @@
#include <errno.h>
#include <fcntl.h>
-#include <gtest/gtest.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
@@ -28,9 +27,10 @@
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
#include "common/test_constants.h"
-#include "otautil/SysUtil.h"
+#include "otautil/sysutil.h"
#include "verifier.h"
using namespace std::string_literals;
diff --git a/tests/manual/recovery_test.cpp b/tests/manual/recovery_test.cpp
index 64e3b59e6..e1d0771e7 100644
--- a/tests/manual/recovery_test.cpp
+++ b/tests/manual/recovery_test.cpp
@@ -14,27 +14,22 @@
* limitations under the License.
*/
-#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
+#include <memory>
#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/";
+static const std::string kInjectTxtFilename = "/data/misc/recovery/inject.txt";
+static const std::string kInjectTxtContent = "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
@@ -44,9 +39,9 @@ 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_NE(std::string::npos, kInjectTxtFilename.find(filename));
+ EXPECT_EQ(kInjectTxtContent, buf);
+ EXPECT_EQ(kInjectTxtContent.size(), len);
EXPECT_EQ(nullptr, arg);
return len;
}
@@ -59,13 +54,14 @@ TEST(recovery, refresh) {
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()));
+ EXPECT_LT(0, __android_log_pmsg_file_write(
+ LOG_ID_SYSTEM, ANDROID_LOG_INFO, kInjectTxtFilename.c_str(),
+ kInjectTxtContent.c_str(), kInjectTxtContent.size()));
- fprintf(stderr, "injected test data, requires two intervening reboots "
- "to check for replication\n");
+ fprintf(stderr,
+ "injected test data, requires two intervening reboots to check for replication\n");
}
- EXPECT_EQ(static_cast<ssize_t>(myContent.size()), ret);
+ EXPECT_EQ(static_cast<ssize_t>(kInjectTxtContent.size()), ret);
}
// recovery.persist - Requires recovery.inject, then a reboot, then
@@ -76,149 +72,18 @@ TEST(recovery, persist) {
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()));
+ EXPECT_LT(0, __android_log_pmsg_file_write(
+ LOG_ID_SYSTEM, ANDROID_LOG_INFO, kInjectTxtFilename.c_str(),
+ kInjectTxtContent.c_str(), kInjectTxtContent.size()));
- fprintf(stderr, "injected test data, requires intervening reboot "
- "to check for storage\n");
+ 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;
+ EXPECT_TRUE(android::base::ReadFileToString(kInjectTxtFilename, &buf));
+ EXPECT_EQ(kInjectTxtContent, buf);
+ if (access(kInjectTxtFilename.c_str(), F_OK) == 0) {
+ fprintf(stderr, "Removing persistent test data, check if reconstructed on reboot\n");
}
- return 1;
+ EXPECT_EQ(0, unlink(kInjectTxtFilename.c_str()));
}
-
-// 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(), "rbe");
- 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));
- 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/font.png b/tests/testdata/font.png
new file mode 100644
index 000000000..d95408a93
--- /dev/null
+++ b/tests/testdata/font.png
Binary files differ
diff --git a/tests/testdata/gzipped_source b/tests/testdata/gzipped_source
new file mode 100644
index 000000000..6d425d059
--- /dev/null
+++ b/tests/testdata/gzipped_source
Binary files differ
diff --git a/tests/testdata/gzipped_target b/tests/testdata/gzipped_target
new file mode 100644
index 000000000..562126286
--- /dev/null
+++ b/tests/testdata/gzipped_target
Binary files differ
diff --git a/tests/testdata/loop00000.png b/tests/testdata/loop00000.png
new file mode 100644
index 000000000..0e11c0100
--- /dev/null
+++ b/tests/testdata/loop00000.png
Binary files differ
diff --git a/tests/testdata/new.file b/tests/testdata/new.file
deleted file mode 100644
index cdeb8fd50..000000000
--- a/tests/testdata/new.file
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/old.file b/tests/testdata/old.file
deleted file mode 100644
index 166c8732e..000000000
--- a/tests/testdata/old.file
+++ /dev/null
Binary files differ
diff --git a/tests/unit/applypatch_test.cpp b/tests/unit/applypatch_test.cpp
new file mode 100644
index 000000000..066f981b4
--- /dev/null
+++ b/tests/unit/applypatch_test.cpp
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agree to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/test_utils.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+
+#include "applypatch/applypatch.h"
+#include "common/test_constants.h"
+#include "edify/expr.h"
+#include "otautil/paths.h"
+#include "otautil/print_sha1.h"
+
+using namespace std::string_literals;
+
+class ApplyPatchTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ source_file = from_testdata_base("boot.img");
+ FileContents boot_fc;
+ ASSERT_TRUE(LoadFileContents(source_file, &boot_fc));
+ source_size = boot_fc.data.size();
+ source_sha1 = print_sha1(boot_fc.sha1);
+
+ target_file = from_testdata_base("recovery.img");
+ FileContents recovery_fc;
+ ASSERT_TRUE(LoadFileContents(target_file, &recovery_fc));
+ target_size = recovery_fc.data.size();
+ target_sha1 = print_sha1(recovery_fc.sha1);
+
+ source_partition = Partition(source_file, source_size, source_sha1);
+ target_partition = Partition(partition_file.path, target_size, target_sha1);
+
+ srand(time(nullptr));
+ bad_sha1_a = android::base::StringPrintf("%040x", rand());
+ bad_sha1_b = android::base::StringPrintf("%040x", rand());
+
+ // Reset the cache backup file.
+ Paths::Get().set_cache_temp_source(cache_temp_source.path);
+ }
+
+ void TearDown() override {
+ ASSERT_TRUE(android::base::RemoveFileIfExists(cache_temp_source.path));
+ }
+
+ std::string source_file;
+ std::string source_sha1;
+ size_t source_size;
+
+ std::string target_file;
+ std::string target_sha1;
+ size_t target_size;
+
+ std::string bad_sha1_a;
+ std::string bad_sha1_b;
+
+ Partition source_partition;
+ Partition target_partition;
+
+ private:
+ TemporaryFile partition_file;
+ TemporaryFile cache_temp_source;
+};
+
+TEST_F(ApplyPatchTest, CheckPartition) {
+ ASSERT_TRUE(CheckPartition(source_partition));
+}
+
+TEST_F(ApplyPatchTest, CheckPartition_Mismatching) {
+ ASSERT_FALSE(CheckPartition(Partition(source_file, target_size, target_sha1)));
+ ASSERT_FALSE(CheckPartition(Partition(source_file, source_size, bad_sha1_a)));
+
+ ASSERT_FALSE(CheckPartition(Partition(source_file, source_size - 1, source_sha1)));
+ ASSERT_FALSE(CheckPartition(Partition(source_file, source_size + 1, source_sha1)));
+}
+
+TEST_F(ApplyPatchTest, PatchPartitionCheck) {
+ ASSERT_TRUE(PatchPartitionCheck(target_partition, source_partition));
+
+ ASSERT_TRUE(
+ PatchPartitionCheck(Partition(source_file, source_size - 1, source_sha1), source_partition));
+
+ ASSERT_TRUE(
+ PatchPartitionCheck(Partition(source_file, source_size + 1, source_sha1), source_partition));
+}
+
+TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup) {
+ ASSERT_FALSE(
+ PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
+
+ Paths::Get().set_cache_temp_source(source_file);
+ ASSERT_TRUE(
+ PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
+}
+
+TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup_BothCorrupted) {
+ ASSERT_FALSE(
+ PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
+
+ Paths::Get().set_cache_temp_source(target_file);
+ ASSERT_FALSE(
+ PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
+}
+
+TEST_F(ApplyPatchTest, PatchPartition) {
+ FileContents patch_fc;
+ ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot.p"), &patch_fc));
+ Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend()));
+
+ FileContents bonus_fc;
+ ASSERT_TRUE(LoadFileContents(from_testdata_base("bonus.file"), &bonus_fc));
+ Value bonus(Value::Type::BLOB, std::string(bonus_fc.data.cbegin(), bonus_fc.data.cend()));
+
+ ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, &bonus));
+}
+
+// Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has
+// everything).
+TEST_F(ApplyPatchTest, PatchPartitionWithoutBonusFile) {
+ FileContents patch_fc;
+ ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot-with-bonus.p"), &patch_fc));
+ Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend()));
+
+ ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, nullptr));
+}
+
+class FreeCacheTest : public ::testing::Test {
+ protected:
+ static constexpr size_t PARTITION_SIZE = 4096 * 10;
+
+ // Returns a sorted list of files in |dirname|.
+ static std::vector<std::string> FindFilesInDir(const std::string& dirname) {
+ std::vector<std::string> file_list;
+
+ std::unique_ptr<DIR, decltype(&closedir)> d(opendir(dirname.c_str()), closedir);
+ struct dirent* de;
+ while ((de = readdir(d.get())) != 0) {
+ std::string path = dirname + "/" + de->d_name;
+
+ struct stat st;
+ if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
+ file_list.emplace_back(de->d_name);
+ }
+ }
+
+ std::sort(file_list.begin(), file_list.end());
+ return file_list;
+ }
+
+ void AddFilesToDir(const std::string& dir, const std::vector<std::string>& files) {
+ std::string zeros(4096, 0);
+ for (const auto& file : files) {
+ temporary_files_.push_back(dir + "/" + file);
+ ASSERT_TRUE(android::base::WriteStringToFile(zeros, temporary_files_.back()));
+ }
+ }
+
+ void SetUp() override {
+ Paths::Get().set_cache_log_directory(mock_log_dir.path);
+ temporary_files_.clear();
+ }
+
+ void TearDown() override {
+ for (const auto& file : temporary_files_) {
+ ASSERT_TRUE(android::base::RemoveFileIfExists(file));
+ }
+ }
+
+ // A mock method to calculate the free space. It assumes the partition has a total size of 40960
+ // bytes and all files are 4096 bytes in size.
+ static size_t MockFreeSpaceChecker(const std::string& dirname) {
+ std::vector<std::string> files = FindFilesInDir(dirname);
+ return PARTITION_SIZE - 4096 * files.size();
+ }
+
+ TemporaryDir mock_cache;
+ TemporaryDir mock_log_dir;
+
+ private:
+ std::vector<std::string> temporary_files_;
+};
+
+TEST_F(FreeCacheTest, FreeCacheSmoke) {
+ std::vector<std::string> files = { "file1", "file2", "file3" };
+ AddFilesToDir(mock_cache.path, files);
+ ASSERT_EQ(files, FindFilesInDir(mock_cache.path));
+ ASSERT_EQ(4096 * 7, MockFreeSpaceChecker(mock_cache.path));
+
+ ASSERT_TRUE(RemoveFilesInDirectory(4096 * 9, mock_cache.path, MockFreeSpaceChecker));
+
+ ASSERT_EQ(std::vector<std::string>{ "file3" }, FindFilesInDir(mock_cache.path));
+ ASSERT_EQ(4096 * 9, MockFreeSpaceChecker(mock_cache.path));
+}
+
+TEST_F(FreeCacheTest, FreeCacheFreeSpaceCheckerError) {
+ std::vector<std::string> files{ "file1", "file2", "file3" };
+ AddFilesToDir(mock_cache.path, files);
+ ASSERT_EQ(files, FindFilesInDir(mock_cache.path));
+ ASSERT_EQ(4096 * 7, MockFreeSpaceChecker(mock_cache.path));
+
+ ASSERT_FALSE(
+ RemoveFilesInDirectory(4096 * 9, mock_cache.path, [](const std::string&) { return -1; }));
+}
+
+TEST_F(FreeCacheTest, FreeCacheOpenFile) {
+ std::vector<std::string> files = { "file1", "file2" };
+ AddFilesToDir(mock_cache.path, files);
+ ASSERT_EQ(files, FindFilesInDir(mock_cache.path));
+ ASSERT_EQ(4096 * 8, MockFreeSpaceChecker(mock_cache.path));
+
+ std::string file1_path = mock_cache.path + "/file1"s;
+ android::base::unique_fd fd(open(file1_path.c_str(), O_RDONLY));
+
+ // file1 can't be deleted as it's opened by us.
+ ASSERT_FALSE(RemoveFilesInDirectory(4096 * 10, mock_cache.path, MockFreeSpaceChecker));
+
+ ASSERT_EQ(std::vector<std::string>{ "file1" }, FindFilesInDir(mock_cache.path));
+}
+
+TEST_F(FreeCacheTest, FreeCacheLogsSmoke) {
+ std::vector<std::string> log_files = { "last_log", "last_log.1", "last_kmsg.2", "last_log.5",
+ "last_log.10" };
+ AddFilesToDir(mock_log_dir.path, log_files);
+ ASSERT_EQ(4096 * 5, MockFreeSpaceChecker(mock_log_dir.path));
+
+ ASSERT_TRUE(RemoveFilesInDirectory(4096 * 8, mock_log_dir.path, MockFreeSpaceChecker));
+
+ // Logs with a higher index will be deleted first
+ std::vector<std::string> expected = { "last_log", "last_log.1" };
+ ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path));
+ ASSERT_EQ(4096 * 8, MockFreeSpaceChecker(mock_log_dir.path));
+}
+
+TEST_F(FreeCacheTest, FreeCacheLogsStringComparison) {
+ std::vector<std::string> log_files = { "last_log.1", "last_kmsg.1", "last_log.not_number",
+ "last_kmsgrandom" };
+ AddFilesToDir(mock_log_dir.path, log_files);
+ ASSERT_EQ(4096 * 6, MockFreeSpaceChecker(mock_log_dir.path));
+
+ ASSERT_TRUE(RemoveFilesInDirectory(4096 * 9, mock_log_dir.path, MockFreeSpaceChecker));
+
+ // Logs with incorrect format will be deleted first; and the last_kmsg with the same index is
+ // deleted before last_log.
+ std::vector<std::string> expected = { "last_log.1" };
+ ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path));
+ ASSERT_EQ(4096 * 9, MockFreeSpaceChecker(mock_log_dir.path));
+}
+
+TEST_F(FreeCacheTest, FreeCacheLogsOtherFiles) {
+ std::vector<std::string> log_files = { "last_install", "command", "block.map", "last_log",
+ "last_kmsg.1" };
+ AddFilesToDir(mock_log_dir.path, log_files);
+ ASSERT_EQ(4096 * 5, MockFreeSpaceChecker(mock_log_dir.path));
+
+ ASSERT_FALSE(RemoveFilesInDirectory(4096 * 8, mock_log_dir.path, MockFreeSpaceChecker));
+
+ // Non log files in /cache/recovery won't be deleted.
+ std::vector<std::string> expected = { "block.map", "command", "last_install" };
+ ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path));
+}
diff --git a/tests/unit/commands_test.cpp b/tests/unit/commands_test.cpp
new file mode 100644
index 000000000..8a54df703
--- /dev/null
+++ b/tests/unit/commands_test.cpp
@@ -0,0 +1,554 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <string>
+
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+#include <openssl/sha.h>
+
+#include "otautil/print_sha1.h"
+#include "otautil/rangeset.h"
+#include "private/commands.h"
+
+TEST(CommandsTest, ParseType) {
+ ASSERT_EQ(Command::Type::ZERO, Command::ParseType("zero"));
+ ASSERT_EQ(Command::Type::NEW, Command::ParseType("new"));
+ ASSERT_EQ(Command::Type::ERASE, Command::ParseType("erase"));
+ ASSERT_EQ(Command::Type::MOVE, Command::ParseType("move"));
+ ASSERT_EQ(Command::Type::BSDIFF, Command::ParseType("bsdiff"));
+ ASSERT_EQ(Command::Type::IMGDIFF, Command::ParseType("imgdiff"));
+ ASSERT_EQ(Command::Type::STASH, Command::ParseType("stash"));
+ ASSERT_EQ(Command::Type::FREE, Command::ParseType("free"));
+ ASSERT_EQ(Command::Type::COMPUTE_HASH_TREE, Command::ParseType("compute_hash_tree"));
+}
+
+TEST(CommandsTest, ParseType_InvalidCommand) {
+ ASSERT_EQ(Command::Type::LAST, Command::ParseType("foo"));
+ ASSERT_EQ(Command::Type::LAST, Command::ParseType("bar"));
+}
+
+TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksOnly) {
+ const std::vector<std::string> tokens{
+ "4,569884,569904,591946,592043",
+ "117",
+ "4,566779,566799,591946,592043",
+ };
+ TargetInfo target;
+ SourceInfo source;
+ std::string err;
+ ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
+ tokens, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
+ "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
+ ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 569884, 569904 }, { 591946, 592043 } })),
+ target);
+ ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), {}, {}),
+ source);
+ ASSERT_EQ(117, source.blocks());
+}
+
+TEST(CommandsTest, ParseTargetInfoAndSourceInfo_StashesOnly) {
+ const std::vector<std::string> tokens{
+ "2,350729,350731",
+ "2",
+ "-",
+ "6ebcf8cf1f6be0bc49e7d4a864214251925d1d15:2,0,2",
+ };
+ TargetInfo target;
+ SourceInfo source;
+ std::string err;
+ ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
+ tokens, "6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", &target,
+ "1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", &source, &err));
+ ASSERT_EQ(
+ TargetInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 350729, 350731 } })),
+ target);
+ ASSERT_EQ(
+ SourceInfo("1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", {}, {},
+ {
+ StashInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 0, 2 } })),
+ }),
+ source);
+ ASSERT_EQ(2, source.blocks());
+}
+
+TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksAndStashes) {
+ const std::vector<std::string> tokens{
+ "4,611641,611643,636981,637075",
+ "96",
+ "4,636981,637075,770665,770666",
+ "4,0,94,95,96",
+ "9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95",
+ };
+ TargetInfo target;
+ SourceInfo source;
+ std::string err;
+ ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
+ tokens, "4734d1b241eb3d0f993714aaf7d665fae43772b6", &target,
+ "a6cbdf3f416960f02189d3a814ec7e9e95c44a0d", &source, &err));
+ ASSERT_EQ(TargetInfo("4734d1b241eb3d0f993714aaf7d665fae43772b6",
+ RangeSet({ { 611641, 611643 }, { 636981, 637075 } })),
+ target);
+ ASSERT_EQ(SourceInfo(
+ "a6cbdf3f416960f02189d3a814ec7e9e95c44a0d",
+ RangeSet({ { 636981, 637075 }, { 770665, 770666 } }), // source ranges
+ RangeSet({ { 0, 94 }, { 95, 96 } }), // source location
+ {
+ StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23", RangeSet({ { 94, 95 } })),
+ }),
+ source);
+ ASSERT_EQ(96, source.blocks());
+}
+
+TEST(CommandsTest, ParseTargetInfoAndSourceInfo_InvalidInput) {
+ const std::vector<std::string> tokens{
+ "4,611641,611643,636981,637075",
+ "96",
+ "4,636981,637075,770665,770666",
+ "4,0,94,95,96",
+ "9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95",
+ };
+ TargetInfo target;
+ SourceInfo source;
+ std::string err;
+
+ // Mismatching block count.
+ {
+ std::vector<std::string> tokens_copy(tokens);
+ tokens_copy[1] = "97";
+ ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
+ tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
+ "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
+ }
+
+ // Excess stashes (causing block count mismatch).
+ {
+ std::vector<std::string> tokens_copy(tokens);
+ tokens_copy.push_back("e145a2f83a33334714ac65e34969c1f115e54a6f:2,0,22");
+ ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
+ tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
+ "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
+ }
+
+ // Invalid args.
+ for (size_t i = 0; i < tokens.size(); i++) {
+ TargetInfo target;
+ SourceInfo source;
+ std::string err;
+ ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
+ std::vector<std::string>(tokens.cbegin() + i + 1, tokens.cend()),
+ "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
+ "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
+ }
+}
+
+TEST(CommandsTest, Parse_EmptyInput) {
+ std::string err;
+ ASSERT_FALSE(Command::Parse("", 0, &err));
+ ASSERT_EQ("invalid type", err);
+}
+
+TEST(CommandsTest, Parse_ABORT_Allowed) {
+ Command::abort_allowed_ = true;
+
+ const std::string input{ "abort" };
+ std::string err;
+ Command command = Command::Parse(input, 0, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(TargetInfo(), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_ABORT_NotAllowed) {
+ const std::string input{ "abort" };
+ std::string err;
+ Command command = Command::Parse(input, 0, &err);
+ ASSERT_FALSE(command);
+}
+
+TEST(CommandsTest, Parse_BSDIFF) {
+ const std::string input{
+ "bsdiff 0 148 "
+ "f201a4e04bd3860da6ad47b957ef424d58a58f8c 9d5d223b4bc5c45dbd25a799c4f1a98466731599 "
+ "4,565704,565752,566779,566799 "
+ "68 4,64525,64545,565704,565752"
+ };
+ std::string err;
+ Command command = Command::Parse(input, 1, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::BSDIFF, command.type());
+ ASSERT_EQ(1, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo("9d5d223b4bc5c45dbd25a799c4f1a98466731599",
+ RangeSet({ { 565704, 565752 }, { 566779, 566799 } })),
+ command.target());
+ ASSERT_EQ(SourceInfo("f201a4e04bd3860da6ad47b957ef424d58a58f8c",
+ RangeSet({ { 64525, 64545 }, { 565704, 565752 } }), RangeSet(), {}),
+ command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(0, 148), command.patch());
+}
+
+TEST(CommandsTest, Parse_ERASE) {
+ const std::string input{ "erase 2,5,10" };
+ std::string err;
+ Command command = Command::Parse(input, 2, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::ERASE, command.type());
+ ASSERT_EQ(2, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 5, 10 } })), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_FREE) {
+ const std::string input{ "free hash1" };
+ std::string err;
+ Command command = Command::Parse(input, 3, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::FREE, command.type());
+ ASSERT_EQ(3, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo(), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo("hash1", RangeSet()), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_IMGDIFF) {
+ const std::string input{
+ "imgdiff 29629269 185 "
+ "a6b1c49aed1b57a2aab1ec3e1505b945540cd8db 51978f65035f584a8ef7afa941dacb6d5e862164 "
+ "2,90851,90852 "
+ "1 2,90851,90852"
+ };
+ std::string err;
+ Command command = Command::Parse(input, 4, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::IMGDIFF, command.type());
+ ASSERT_EQ(4, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo("51978f65035f584a8ef7afa941dacb6d5e862164", RangeSet({ { 90851, 90852 } })),
+ command.target());
+ ASSERT_EQ(SourceInfo("a6b1c49aed1b57a2aab1ec3e1505b945540cd8db", RangeSet({ { 90851, 90852 } }),
+ RangeSet(), {}),
+ command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(29629269, 185), command.patch());
+}
+
+TEST(CommandsTest, Parse_MOVE) {
+ const std::string input{
+ "move 1d74d1a60332fd38cf9405f1bae67917888da6cb "
+ "4,569884,569904,591946,592043 117 4,566779,566799,591946,592043"
+ };
+ std::string err;
+ Command command = Command::Parse(input, 5, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::MOVE, command.type());
+ ASSERT_EQ(5, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 569884, 569904 }, { 591946, 592043 } })),
+ command.target());
+ ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), RangeSet(), {}),
+ command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_NEW) {
+ const std::string input{ "new 4,3,5,10,12" };
+ std::string err;
+ Command command = Command::Parse(input, 6, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::NEW, command.type());
+ ASSERT_EQ(6, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 3, 5 }, { 10, 12 } })), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_STASH) {
+ const std::string input{ "stash hash1 2,5,10" };
+ std::string err;
+ Command command = Command::Parse(input, 7, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::STASH, command.type());
+ ASSERT_EQ(7, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo(), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo("hash1", RangeSet({ { 5, 10 } })), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_ZERO) {
+ const std::string input{ "zero 2,1,5" };
+ std::string err;
+ Command command = Command::Parse(input, 8, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::ZERO, command.type());
+ ASSERT_EQ(8, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 1, 5 } })), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_COMPUTE_HASH_TREE) {
+ const std::string input{ "compute_hash_tree 2,0,1 2,3,4 sha1 unknown-salt unknown-root-hash" };
+ std::string err;
+ Command command = Command::Parse(input, 9, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::COMPUTE_HASH_TREE, command.type());
+ ASSERT_EQ(9, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ HashTreeInfo expected_info(RangeSet({ { 0, 1 } }), RangeSet({ { 3, 4 } }), "sha1", "unknown-salt",
+ "unknown-root-hash");
+ ASSERT_EQ(expected_info, command.hash_tree_info());
+ ASSERT_EQ(TargetInfo(), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_InvalidNumberOfArgs) {
+ Command::abort_allowed_ = true;
+
+ // Note that the case of having excess args in BSDIFF, IMGDIFF and MOVE is covered by
+ // ParseTargetInfoAndSourceInfo_InvalidInput.
+ std::vector<std::string> inputs{
+ "abort foo",
+ "bsdiff",
+ "compute_hash_tree, 2,0,1 2,0,1 unknown-algorithm unknown-salt",
+ "erase",
+ "erase 4,3,5,10,12 hash1",
+ "free",
+ "free id1 id2",
+ "imgdiff",
+ "move",
+ "new",
+ "new 4,3,5,10,12 hash1",
+ "stash",
+ "stash id1",
+ "stash id1 4,3,5,10,12 id2",
+ "zero",
+ "zero 4,3,5,10,12 hash2",
+ };
+ for (const auto& input : inputs) {
+ std::string err;
+ ASSERT_FALSE(Command::Parse(input, 0, &err));
+ }
+}
+
+TEST(SourceInfoTest, Overlaps) {
+ ASSERT_TRUE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
+ .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 7, 9 }, { 16, 20 } }))));
+
+ ASSERT_TRUE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
+ .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 4, 7 }, { 16, 23 } }))));
+
+ ASSERT_FALSE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
+ .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 9, 16 } }))));
+}
+
+TEST(SourceInfoTest, Overlaps_EmptySourceOrTarget) {
+ ASSERT_FALSE(SourceInfo().Overlaps(TargetInfo()));
+
+ ASSERT_FALSE(SourceInfo().Overlaps(
+ TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", RangeSet({ { 7, 9 }, { 16, 20 } }))));
+
+ ASSERT_FALSE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
+ .Overlaps(TargetInfo()));
+}
+
+TEST(SourceInfoTest, Overlaps_WithStashes) {
+ ASSERT_FALSE(SourceInfo("a6cbdf3f416960f02189d3a814ec7e9e95c44a0d",
+ RangeSet({ { 81, 175 }, { 265, 266 } }), // source ranges
+ RangeSet({ { 0, 94 }, { 95, 96 } }), // source location
+ { StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23",
+ RangeSet({ { 94, 95 } })) })
+ .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 175, 265 } }))));
+
+ ASSERT_TRUE(SourceInfo("a6cbdf3f416960f02189d3a814ec7e9e95c44a0d",
+ RangeSet({ { 81, 175 }, { 265, 266 } }), // source ranges
+ RangeSet({ { 0, 94 }, { 95, 96 } }), // source location
+ { StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23",
+ RangeSet({ { 94, 95 } })) })
+ .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 265, 266 } }))));
+}
+
+// The block size should be specified by the caller of ReadAll (i.e. from Command instance during
+// normal run).
+constexpr size_t kBlockSize = 4096;
+
+TEST(SourceInfoTest, ReadAll) {
+ // "2727756cfee3fbfe24bf5650123fd7743d7b3465" is the SHA-1 hex digest of 8192 * 'a'.
+ const SourceInfo source("2727756cfee3fbfe24bf5650123fd7743d7b3465", RangeSet({ { 0, 2 } }), {},
+ {});
+ auto block_reader = [](const RangeSet& src, std::vector<uint8_t>* block_buffer) -> int {
+ std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a');
+ return 0;
+ };
+ auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
+ std::vector<uint8_t> buffer(source.blocks() * kBlockSize);
+ ASSERT_TRUE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader));
+ ASSERT_EQ(source.blocks() * kBlockSize, buffer.size());
+
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(buffer.data(), buffer.size(), digest);
+ ASSERT_EQ(source.hash(), print_sha1(digest));
+}
+
+TEST(SourceInfoTest, ReadAll_WithStashes) {
+ const SourceInfo source(
+ // SHA-1 hex digest of 8192 * 'a' + 4096 * 'b'.
+ "ee3ebea26130769c10ad13604712100346d48660", RangeSet({ { 0, 2 } }), RangeSet({ { 0, 2 } }),
+ { StashInfo("1e41f7a59e80c6eb4dc043caae80d273f130bed8", RangeSet({ { 2, 3 } })) });
+ auto block_reader = [](const RangeSet& src, std::vector<uint8_t>* block_buffer) -> int {
+ std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a');
+ return 0;
+ };
+ auto stash_reader = [](const std::string&, std::vector<uint8_t>* stash_buffer) -> int {
+ std::fill_n(stash_buffer->begin(), kBlockSize, 'b');
+ return 0;
+ };
+ std::vector<uint8_t> buffer(source.blocks() * kBlockSize);
+ ASSERT_TRUE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader));
+ ASSERT_EQ(source.blocks() * kBlockSize, buffer.size());
+
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(buffer.data(), buffer.size(), digest);
+ ASSERT_EQ(source.hash(), print_sha1(digest));
+}
+
+TEST(SourceInfoTest, ReadAll_BufferTooSmall) {
+ const SourceInfo source("2727756cfee3fbfe24bf5650123fd7743d7b3465", RangeSet({ { 0, 2 } }), {},
+ {});
+ auto block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return 0; };
+ auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
+ std::vector<uint8_t> buffer(source.blocks() * kBlockSize - 1);
+ ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader));
+}
+
+TEST(SourceInfoTest, ReadAll_FailingReader) {
+ const SourceInfo source(
+ "ee3ebea26130769c10ad13604712100346d48660", RangeSet({ { 0, 2 } }), RangeSet({ { 0, 2 } }),
+ { StashInfo("1e41f7a59e80c6eb4dc043caae80d273f130bed8", RangeSet({ { 2, 3 } })) });
+ std::vector<uint8_t> buffer(source.blocks() * kBlockSize);
+ auto failing_block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return -1; };
+ auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
+ ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, failing_block_reader, stash_reader));
+
+ auto block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return 0; };
+ auto failing_stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return -1; };
+ ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, block_reader, failing_stash_reader));
+}
+
+TEST(TransferListTest, Parse) {
+ std::vector<std::string> input_lines{
+ "4", // version
+ "2", // total blocks
+ "1", // max stashed entries
+ "1", // max stashed blocks
+ "stash 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1",
+ "move 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1 1 2,0,1",
+ };
+
+ std::string err;
+ TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err);
+ ASSERT_TRUE(static_cast<bool>(transfer_list));
+ ASSERT_EQ(4, transfer_list.version());
+ ASSERT_EQ(2, transfer_list.total_blocks());
+ ASSERT_EQ(1, transfer_list.stash_max_entries());
+ ASSERT_EQ(1, transfer_list.stash_max_blocks());
+ ASSERT_EQ(2U, transfer_list.commands().size());
+ ASSERT_EQ(Command::Type::STASH, transfer_list.commands()[0].type());
+ ASSERT_EQ(Command::Type::MOVE, transfer_list.commands()[1].type());
+}
+
+TEST(TransferListTest, Parse_InvalidCommand) {
+ std::vector<std::string> input_lines{
+ "4", // version
+ "2", // total blocks
+ "1", // max stashed entries
+ "1", // max stashed blocks
+ "stash 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1",
+ "move 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1 1",
+ };
+
+ std::string err;
+ TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err);
+ ASSERT_FALSE(static_cast<bool>(transfer_list));
+}
+
+TEST(TransferListTest, Parse_ZeroTotalBlocks) {
+ std::vector<std::string> input_lines{
+ "4", // version
+ "0", // total blocks
+ "0", // max stashed entries
+ "0", // max stashed blocks
+ };
+
+ std::string err;
+ TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err);
+ ASSERT_TRUE(static_cast<bool>(transfer_list));
+ ASSERT_EQ(4, transfer_list.version());
+ ASSERT_EQ(0, transfer_list.total_blocks());
+ ASSERT_EQ(0, transfer_list.stash_max_entries());
+ ASSERT_EQ(0, transfer_list.stash_max_blocks());
+ ASSERT_TRUE(transfer_list.commands().empty());
+}
diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp
index 7f85d13ea..1ca786c28 100644
--- a/tests/unit/dirutil_test.cpp
+++ b/tests/unit/dirutil_test.cpp
@@ -22,7 +22,8 @@
#include <android-base/test_utils.h>
#include <gtest/gtest.h>
-#include <otautil/DirUtil.h>
+
+#include "otautil/dirutil.h"
TEST(DirUtilTest, create_invalid) {
// Requesting to create an empty dir is invalid.
diff --git a/tests/unit/parse_install_logs_test.cpp b/tests/unit/parse_install_logs_test.cpp
new file mode 100644
index 000000000..8061f3be1
--- /dev/null
+++ b/tests/unit/parse_install_logs_test.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+
+#include "otautil/parse_install_logs.h"
+
+TEST(ParseInstallLogsTest, EmptyFile) {
+ TemporaryFile last_install;
+
+ auto metrics = ParseLastInstall(last_install.path);
+ ASSERT_TRUE(metrics.empty());
+}
+
+TEST(ParseInstallLogsTest, SideloadSmoke) {
+ TemporaryFile last_install;
+ ASSERT_TRUE(android::base::WriteStringToFile("/cache/recovery/ota.zip\n0\n", last_install.path));
+ auto metrics = ParseLastInstall(last_install.path);
+ ASSERT_EQ(metrics.end(), metrics.find("ota_sideload"));
+
+ ASSERT_TRUE(android::base::WriteStringToFile("/sideload/package.zip\n0\n", last_install.path));
+ metrics = ParseLastInstall(last_install.path);
+ ASSERT_NE(metrics.end(), metrics.find("ota_sideload"));
+}
+
+TEST(ParseInstallLogsTest, ParseRecoveryUpdateMetrics) {
+ std::vector<std::string> lines = {
+ "/sideload/package.zip",
+ "0",
+ "time_total: 300",
+ "uncrypt_time: 40",
+ "source_build: 4973410",
+ "bytes_written_system: " + std::to_string(1200 * 1024 * 1024),
+ "bytes_stashed_system: " + std::to_string(300 * 1024 * 1024),
+ "bytes_written_vendor: " + std::to_string(40 * 1024 * 1024),
+ "bytes_stashed_vendor: " + std::to_string(50 * 1024 * 1024),
+ "temperature_start: 37000",
+ "temperature_end: 38000",
+ "temperature_max: 39000",
+ "error: 22",
+ "cause: 55",
+ };
+
+ auto metrics = ParseRecoveryUpdateMetrics(lines);
+
+ std::map<std::string, int64_t> expected_result = {
+ { "ota_time_total", 300 }, { "ota_uncrypt_time", 40 },
+ { "ota_source_version", 4973410 }, { "ota_written_in_MiBs", 1240 },
+ { "ota_stashed_in_MiBs", 350 }, { "ota_temperature_start", 37000 },
+ { "ota_temperature_end", 38000 }, { "ota_temperature_max", 39000 },
+ { "ota_non_ab_error_code", 22 }, { "ota_non_ab_cause_code", 55 },
+ };
+
+ ASSERT_EQ(expected_result, metrics);
+}
diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp
index 7ae193e18..fc72f2f6d 100644
--- a/tests/unit/rangeset_test.cpp
+++ b/tests/unit/rangeset_test.cpp
@@ -209,6 +209,7 @@ TEST(RangeSetTest, GetBlockNumber) {
ASSERT_EQ(static_cast<size_t>(6), rs.GetBlockNumber(5));
ASSERT_EQ(static_cast<size_t>(9), rs.GetBlockNumber(8));
+ ::testing::FLAGS_gtest_death_test_style = "threadsafe";
// Out of bound.
ASSERT_EXIT(rs.GetBlockNumber(9), ::testing::KilledBySignal(SIGABRT), "");
}
@@ -284,6 +285,8 @@ TEST(SortedRangeSetTest, file_range) {
ASSERT_EQ(static_cast<size_t>(10), rs.GetOffsetInRangeSet(4106));
ASSERT_EQ(static_cast<size_t>(40970), rs.GetOffsetInRangeSet(4096 * 16 + 10));
+
+ ::testing::FLAGS_gtest_death_test_style = "threadsafe";
// block#10 not in range.
ASSERT_EXIT(rs.GetOffsetInRangeSet(40970), ::testing::KilledBySignal(SIGABRT), "");
}
diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp
new file mode 100644
index 000000000..7d97a006b
--- /dev/null
+++ b/tests/unit/screen_ui_test.cpp
@@ -0,0 +1,494 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+
+#include "common/test_constants.h"
+#include "device.h"
+#include "minui/minui.h"
+#include "otautil/paths.h"
+#include "private/resources.h"
+#include "screen_ui.h"
+
+static const std::vector<std::string> HEADERS{ "header" };
+static const std::vector<std::string> ITEMS{ "item1", "item2", "item3", "item4", "1234567890" };
+
+TEST(ScreenUITest, StartPhoneMenuSmoke) {
+ Menu menu(false, 10, 20, HEADERS, ITEMS, 0);
+ ASSERT_FALSE(menu.scrollable());
+ ASSERT_EQ(HEADERS[0], menu.text_headers()[0]);
+ ASSERT_EQ(5u, menu.ItemsCount());
+
+ std::string message;
+ ASSERT_FALSE(menu.ItemsOverflow(&message));
+ for (size_t i = 0; i < menu.ItemsCount(); i++) {
+ ASSERT_EQ(ITEMS[i], menu.TextItem(i));
+ }
+
+ ASSERT_EQ(0, menu.selection());
+}
+
+TEST(ScreenUITest, StartWearMenuSmoke) {
+ Menu menu(true, 10, 8, HEADERS, ITEMS, 1);
+ ASSERT_TRUE(menu.scrollable());
+ ASSERT_EQ(HEADERS[0], menu.text_headers()[0]);
+ ASSERT_EQ(5u, menu.ItemsCount());
+
+ std::string message;
+ ASSERT_FALSE(menu.ItemsOverflow(&message));
+ for (size_t i = 0; i < menu.ItemsCount() - 1; i++) {
+ ASSERT_EQ(ITEMS[i], menu.TextItem(i));
+ }
+ // Test of the last item is truncated
+ ASSERT_EQ("12345678", menu.TextItem(4));
+ ASSERT_EQ(1, menu.selection());
+}
+
+TEST(ScreenUITest, StartPhoneMenuItemsOverflow) {
+ Menu menu(false, 1, 20, HEADERS, ITEMS, 0);
+ ASSERT_FALSE(menu.scrollable());
+ ASSERT_EQ(1u, menu.ItemsCount());
+
+ std::string message;
+ ASSERT_FALSE(menu.ItemsOverflow(&message));
+ for (size_t i = 0; i < menu.ItemsCount(); i++) {
+ ASSERT_EQ(ITEMS[i], menu.TextItem(i));
+ }
+
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(1u, menu.MenuEnd());
+}
+
+TEST(ScreenUITest, StartWearMenuItemsOverflow) {
+ Menu menu(true, 1, 20, HEADERS, ITEMS, 0);
+ ASSERT_TRUE(menu.scrollable());
+ ASSERT_EQ(5u, menu.ItemsCount());
+
+ std::string message;
+ ASSERT_TRUE(menu.ItemsOverflow(&message));
+ ASSERT_EQ("Current item: 1/5", message);
+
+ for (size_t i = 0; i < menu.ItemsCount(); i++) {
+ ASSERT_EQ(ITEMS[i], menu.TextItem(i));
+ }
+
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(1u, menu.MenuEnd());
+}
+
+TEST(ScreenUITest, PhoneMenuSelectSmoke) {
+ int sel = 0;
+ Menu menu(false, 10, 20, HEADERS, ITEMS, sel);
+ // Mimic down button 10 times (2 * items size)
+ for (int i = 0; i < 10; i++) {
+ sel = menu.Select(++sel);
+ ASSERT_EQ(sel, menu.selection());
+
+ // Wraps the selection for unscrollable menu when it reaches the boundary.
+ int expected = (i + 1) % 5;
+ ASSERT_EQ(expected, menu.selection());
+
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(5u, menu.MenuEnd());
+ }
+
+ // Mimic up button 10 times
+ for (int i = 0; i < 10; i++) {
+ sel = menu.Select(--sel);
+ ASSERT_EQ(sel, menu.selection());
+
+ int expected = (9 - i) % 5;
+ ASSERT_EQ(expected, menu.selection());
+
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(5u, menu.MenuEnd());
+ }
+}
+
+TEST(ScreenUITest, WearMenuSelectSmoke) {
+ int sel = 0;
+ Menu menu(true, 10, 20, HEADERS, ITEMS, sel);
+ // Mimic pressing down button 10 times (2 * items size)
+ for (int i = 0; i < 10; i++) {
+ sel = menu.Select(++sel);
+ ASSERT_EQ(sel, menu.selection());
+
+ // Stops the selection at the boundary if the menu is scrollable.
+ int expected = std::min(i + 1, 4);
+ ASSERT_EQ(expected, menu.selection());
+
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(5u, menu.MenuEnd());
+ }
+
+ // Mimic pressing up button 10 times
+ for (int i = 0; i < 10; i++) {
+ sel = menu.Select(--sel);
+ ASSERT_EQ(sel, menu.selection());
+
+ int expected = std::max(3 - i, 0);
+ ASSERT_EQ(expected, menu.selection());
+
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(5u, menu.MenuEnd());
+ }
+}
+
+TEST(ScreenUITest, WearMenuSelectItemsOverflow) {
+ int sel = 1;
+ Menu menu(true, 3, 20, HEADERS, ITEMS, sel);
+ ASSERT_EQ(5u, menu.ItemsCount());
+
+ // Scroll the menu to the end, and check the start & end of menu.
+ for (int i = 0; i < 3; i++) {
+ sel = menu.Select(++sel);
+ ASSERT_EQ(i + 2, sel);
+ ASSERT_EQ(static_cast<size_t>(i), menu.MenuStart());
+ ASSERT_EQ(static_cast<size_t>(i + 3), menu.MenuEnd());
+ }
+
+ // Press down button one more time won't change the MenuStart() and MenuEnd().
+ sel = menu.Select(++sel);
+ ASSERT_EQ(4, sel);
+ ASSERT_EQ(2u, menu.MenuStart());
+ ASSERT_EQ(5u, menu.MenuEnd());
+
+ // Scroll the menu to the top.
+ // The expected menu sel, start & ends are:
+ // sel 3, start 2, end 5
+ // sel 2, start 2, end 5
+ // sel 1, start 1, end 4
+ // sel 0, start 0, end 3
+ for (int i = 0; i < 4; i++) {
+ sel = menu.Select(--sel);
+ ASSERT_EQ(3 - i, sel);
+ ASSERT_EQ(static_cast<size_t>(std::min(3 - i, 2)), menu.MenuStart());
+ ASSERT_EQ(static_cast<size_t>(std::min(6 - i, 5)), menu.MenuEnd());
+ }
+
+ // Press up button one more time won't change the MenuStart() and MenuEnd().
+ sel = menu.Select(--sel);
+ ASSERT_EQ(0, sel);
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(3u, menu.MenuEnd());
+}
+
+static constexpr int kMagicAction = 101;
+
+enum class KeyCode : int {
+ TIMEOUT = -1,
+ NO_OP = 0,
+ UP = 1,
+ DOWN = 2,
+ ENTER = 3,
+ MAGIC = 1001,
+ LAST,
+};
+
+static const std::map<KeyCode, int> kKeyMapping{
+ // clang-format off
+ { KeyCode::NO_OP, Device::kNoAction },
+ { KeyCode::UP, Device::kHighlightUp },
+ { KeyCode::DOWN, Device::kHighlightDown },
+ { KeyCode::ENTER, Device::kInvokeItem },
+ { KeyCode::MAGIC, kMagicAction },
+ // clang-format on
+};
+
+class TestableScreenRecoveryUI : public ScreenRecoveryUI {
+ public:
+ int WaitKey() override;
+
+ void SetKeyBuffer(const std::vector<KeyCode>& buffer);
+
+ int KeyHandler(int key, bool visible) const;
+
+ // The following functions expose the protected members for test purpose.
+ void RunLoadAnimation() {
+ LoadAnimation();
+ }
+
+ size_t GetLoopFrames() const {
+ return loop_frames;
+ }
+
+ size_t GetIntroFrames() const {
+ return intro_frames;
+ }
+
+ bool GetRtlLocale() const {
+ return rtl_locale_;
+ }
+
+ private:
+ std::vector<KeyCode> key_buffer_;
+ size_t key_buffer_index_;
+};
+
+void TestableScreenRecoveryUI::SetKeyBuffer(const std::vector<KeyCode>& buffer) {
+ key_buffer_ = buffer;
+ key_buffer_index_ = 0;
+}
+
+int TestableScreenRecoveryUI::KeyHandler(int key, bool) const {
+ KeyCode key_code = static_cast<KeyCode>(key);
+ if (kKeyMapping.find(key_code) != kKeyMapping.end()) {
+ return kKeyMapping.at(key_code);
+ }
+ return Device::kNoAction;
+}
+
+int TestableScreenRecoveryUI::WaitKey() {
+ if (IsKeyInterrupted()) {
+ return static_cast<int>(RecoveryUI::KeyError::INTERRUPTED);
+ }
+
+ CHECK_LT(key_buffer_index_, key_buffer_.size());
+ return static_cast<int>(key_buffer_[key_buffer_index_++]);
+}
+
+class ScreenRecoveryUITest : public ::testing::Test {
+ protected:
+ const std::string kTestLocale = "en-US";
+ const std::string kTestRtlLocale = "ar";
+ const std::string kTestRtlLocaleWithSuffix = "ar-EG";
+
+ void SetUp() override {
+ has_graphics_ = gr_init() == 0;
+ gr_exit();
+
+ if (has_graphics_) {
+ ui_ = std::make_unique<TestableScreenRecoveryUI>();
+ }
+
+ testdata_dir_ = from_testdata_base("");
+ Paths::Get().set_resource_dir(testdata_dir_);
+ res_set_resource_dir(testdata_dir_);
+ }
+
+ bool has_graphics_;
+ std::unique_ptr<TestableScreenRecoveryUI> ui_;
+ std::string testdata_dir_;
+};
+
+#define RETURN_IF_NO_GRAPHICS \
+ do { \
+ if (!has_graphics_) { \
+ GTEST_LOG_(INFO) << "Test skipped due to no available graphics device"; \
+ return; \
+ } \
+ } while (false)
+
+TEST_F(ScreenRecoveryUITest, Init) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ASSERT_EQ(kTestLocale, ui_->GetLocale());
+ ASSERT_FALSE(ui_->GetRtlLocale());
+ ASSERT_FALSE(ui_->IsTextVisible());
+ ASSERT_FALSE(ui_->WasTextEverVisible());
+}
+
+TEST_F(ScreenRecoveryUITest, dtor_NotCallingInit) {
+ ui_.reset();
+ ASSERT_FALSE(ui_);
+}
+
+TEST_F(ScreenRecoveryUITest, ShowText) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ASSERT_FALSE(ui_->IsTextVisible());
+ ui_->ShowText(true);
+ ASSERT_TRUE(ui_->IsTextVisible());
+ ASSERT_TRUE(ui_->WasTextEverVisible());
+
+ ui_->ShowText(false);
+ ASSERT_FALSE(ui_->IsTextVisible());
+ ASSERT_TRUE(ui_->WasTextEverVisible());
+}
+
+TEST_F(ScreenRecoveryUITest, RtlLocale) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestRtlLocale));
+ ASSERT_TRUE(ui_->GetRtlLocale());
+}
+
+TEST_F(ScreenRecoveryUITest, RtlLocaleWithSuffix) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestRtlLocaleWithSuffix));
+ ASSERT_TRUE(ui_->GetRtlLocale());
+}
+
+TEST_F(ScreenRecoveryUITest, ShowMenu) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ui_->SetKeyBuffer({
+ KeyCode::UP,
+ KeyCode::DOWN,
+ KeyCode::UP,
+ KeyCode::DOWN,
+ KeyCode::ENTER,
+ });
+ ASSERT_EQ(3u, ui_->ShowMenu(HEADERS, ITEMS, 3, true,
+ std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+ std::placeholders::_1, std::placeholders::_2)));
+
+ ui_->SetKeyBuffer({
+ KeyCode::UP,
+ KeyCode::UP,
+ KeyCode::NO_OP,
+ KeyCode::NO_OP,
+ KeyCode::UP,
+ KeyCode::ENTER,
+ });
+ ASSERT_EQ(2u, ui_->ShowMenu(HEADERS, ITEMS, 0, true,
+ std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+ std::placeholders::_1, std::placeholders::_2)));
+}
+
+TEST_F(ScreenRecoveryUITest, ShowMenu_NotMenuOnly) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ui_->SetKeyBuffer({
+ KeyCode::MAGIC,
+ });
+ ASSERT_EQ(static_cast<size_t>(kMagicAction),
+ ui_->ShowMenu(HEADERS, ITEMS, 3, false,
+ std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+ std::placeholders::_1, std::placeholders::_2)));
+}
+
+TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ui_->SetKeyBuffer({
+ KeyCode::TIMEOUT,
+ });
+ ASSERT_EQ(static_cast<size_t>(RecoveryUI::KeyError::TIMED_OUT),
+ ui_->ShowMenu(HEADERS, ITEMS, 3, true, nullptr));
+}
+
+TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ui_->ShowText(true);
+ ui_->ShowText(false);
+ ASSERT_TRUE(ui_->WasTextEverVisible());
+
+ ui_->SetKeyBuffer({
+ KeyCode::TIMEOUT,
+ KeyCode::DOWN,
+ KeyCode::ENTER,
+ });
+ ASSERT_EQ(4u, ui_->ShowMenu(HEADERS, ITEMS, 3, true,
+ std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+ std::placeholders::_1, std::placeholders::_2)));
+}
+
+TEST_F(ScreenRecoveryUITest, ShowMenuWithInterrupt) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ui_->SetKeyBuffer({
+ KeyCode::UP,
+ KeyCode::DOWN,
+ KeyCode::UP,
+ KeyCode::DOWN,
+ KeyCode::ENTER,
+ });
+
+ ui_->InterruptKey();
+ ASSERT_EQ(static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED),
+ ui_->ShowMenu(HEADERS, ITEMS, 3, true,
+ std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+ std::placeholders::_1, std::placeholders::_2)));
+
+ ui_->SetKeyBuffer({
+ KeyCode::UP,
+ KeyCode::UP,
+ KeyCode::NO_OP,
+ KeyCode::NO_OP,
+ KeyCode::UP,
+ KeyCode::ENTER,
+ });
+ ASSERT_EQ(static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED),
+ ui_->ShowMenu(HEADERS, ITEMS, 0, true,
+ std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+ std::placeholders::_1, std::placeholders::_2)));
+}
+
+TEST_F(ScreenRecoveryUITest, LoadAnimation) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ // Make a few copies of loop00000.png from testdata.
+ std::string image_data;
+ ASSERT_TRUE(android::base::ReadFileToString(testdata_dir_ + "/loop00000.png", &image_data));
+
+ std::vector<std::string> tempfiles;
+ TemporaryDir resource_dir;
+ for (const auto& name : { "00002", "00100", "00050" }) {
+ tempfiles.push_back(android::base::StringPrintf("%s/loop%s.png", resource_dir.path, name));
+ ASSERT_TRUE(android::base::WriteStringToFile(image_data, tempfiles.back()));
+ }
+ for (const auto& name : { "00", "01" }) {
+ tempfiles.push_back(android::base::StringPrintf("%s/intro%s.png", resource_dir.path, name));
+ ASSERT_TRUE(android::base::WriteStringToFile(image_data, tempfiles.back()));
+ }
+ Paths::Get().set_resource_dir(resource_dir.path);
+
+ ui_->RunLoadAnimation();
+
+ ASSERT_EQ(2u, ui_->GetIntroFrames());
+ ASSERT_EQ(3u, ui_->GetLoopFrames());
+
+ for (const auto& name : tempfiles) {
+ ASSERT_EQ(0, unlink(name.c_str()));
+ }
+}
+
+TEST_F(ScreenRecoveryUITest, LoadAnimation_MissingAnimation) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ // We need a dir that doesn't contain any animation. However, using TemporaryDir will give
+ // leftovers since this is a death test where TemporaryDir::~TemporaryDir() won't be called.
+ Paths::Get().set_resource_dir("/proc/self");
+
+ ::testing::FLAGS_gtest_death_test_style = "threadsafe";
+ ASSERT_EXIT(ui_->RunLoadAnimation(), ::testing::KilledBySignal(SIGABRT), "");
+}
+
+#undef RETURN_IF_NO_GRAPHICS
diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp
index 434ee25bf..de8ff7065 100644
--- a/tests/unit/sysutil_test.cpp
+++ b/tests/unit/sysutil_test.cpp
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
-
#include <string>
#include <android-base/file.h>
#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
-#include "otautil/SysUtil.h"
+#include "otautil/sysutil.h"
TEST(SysUtilTest, InvalidArgs) {
MemMapping mapping;
@@ -128,3 +127,13 @@ TEST(SysUtilTest, MapFileBlockMapInvalidBlockMap) {
ASSERT_TRUE(android::base::WriteStringToFile("/doesntexist\n4096 4096\n1\n0 1\n", temp_file.path));
ASSERT_FALSE(mapping.MapFile(filename));
}
+
+TEST(SysUtilTest, StringVectorToNullTerminatedArray) {
+ std::vector<std::string> args{ "foo", "bar", "baz" };
+ auto args_with_nullptr = StringVectorToNullTerminatedArray(args);
+ ASSERT_EQ(4, args_with_nullptr.size());
+ ASSERT_STREQ("foo", args_with_nullptr[0]);
+ ASSERT_STREQ("bar", args_with_nullptr[1]);
+ ASSERT_STREQ("baz", args_with_nullptr[2]);
+ ASSERT_EQ(nullptr, args_with_nullptr[3]);
+}
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
index 827668521..47f33d9ea 100644
--- a/tests/unit/zip_test.cpp
+++ b/tests/unit/zip_test.cpp
@@ -23,10 +23,10 @@
#include <android-base/file.h>
#include <android-base/test_utils.h>
#include <gtest/gtest.h>
-#include <otautil/SysUtil.h>
#include <ziparchive/zip_archive.h>
#include "common/test_constants.h"
+#include "otautil/sysutil.h"
TEST(ZipTest, OpenFromMemory) {
std::string zip_path = from_testdata_base("ziptest_dummy-update.zip");