From 6cd816859e462e8741ced8dc24c00b7cd8304e2a Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Thu, 3 May 2018 21:53:11 -0700 Subject: [PATCH] tests: Add tests for ScreenRecoveryUI. In order to support that, this CL adds Paths::set_resource_dir() to override the default resource dir ("/res/images/") that's only available under recovery. Note that since there're external modules depending on libminui, it adds a separate function of res_set_resource_dir(), instead of requiring the dependency on libotautil for everyone. Test: mmma -j bootable/recovery Test: Run recovery_unit_test on marlin. Change-Id: I0a7dcf4476808bea9e634eaffc9676f6cbaf92b7 --- Android.mk | 22 ++-- minui/include/private/resources.h | 3 + minui/resources.cpp | 9 +- otautil/include/otautil/paths.h | 10 ++ otautil/paths.cpp | 2 + screen_ui.cpp | 6 +- tests/Android.mk | 1 + tests/testdata/font.png | Bin 0 -> 24437 bytes tests/testdata/loop00000.png | Bin 0 -> 5900 bytes tests/unit/screen_ui_test.cpp | 167 ++++++++++++++++++++++++++++++ 10 files changed, 205 insertions(+), 15 deletions(-) create mode 100644 tests/testdata/font.png create mode 100644 tests/testdata/loop00000.png diff --git a/Android.mk b/Android.mk index afbc950f..e6bea072 100644 --- a/Android.mk +++ b/Android.mk @@ -156,20 +156,20 @@ LOCAL_C_INCLUDES += \ LOCAL_STATIC_LIBRARIES := \ librecovery \ $(TARGET_RECOVERY_UI_LIB) \ - libverifier \ - libbatterymonitor \ - libbootloader_message \ - libfs_mgr \ - libext4_utils \ - libsparse \ - libziparchive \ - libotautil \ - libminadbd \ - libasyncio \ - libfusesideload \ librecovery_ui \ libminui \ + libverifier \ + libbootloader_message \ + libfusesideload \ + libminadbd \ + libotautil \ + libasyncio \ + libbatterymonitor \ + libfs_mgr \ + libext4_utils \ libpng \ + libsparse \ + libziparchive \ libcrypto_utils \ libcrypto \ libvintf_recovery \ diff --git a/minui/include/private/resources.h b/minui/include/private/resources.h index 2a83a102..047ebe2e 100644 --- a/minui/include/private/resources.h +++ b/minui/include/private/resources.h @@ -82,3 +82,6 @@ class PngHandler { // After initialization, we'll keep the file pointer open before destruction of PngHandler. std::unique_ptr png_fp_{ nullptr, fclose }; }; + +// Overrides the default resource dir, for testing purpose. +void res_set_resource_dir(const std::string&); diff --git a/minui/resources.cpp b/minui/resources.cpp index 9f67cf84..c018d9b8 100644 --- a/minui/resources.cpp +++ b/minui/resources.cpp @@ -32,7 +32,6 @@ #include #include -#include #include #include @@ -40,6 +39,8 @@ #define SURFACE_DATA_ALIGNMENT 8 +static std::string g_resource_dir{ "/res/images" }; + static GRSurface* malloc_surface(size_t data_size) { size_t size = sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT; unsigned char* temp = static_cast(malloc(size)); @@ -51,7 +52,7 @@ static GRSurface* malloc_surface(size_t data_size) { } PngHandler::PngHandler(const std::string& name) { - std::string res_path = android::base::StringPrintf("/res/images/%s.png", name.c_str()); + std::string res_path = g_resource_dir + "/" + name + ".png"; png_fp_.reset(fopen(res_path.c_str(), "rbe")); // Try to read from |name| if the resource path does not work. if (!png_fp_) { @@ -340,6 +341,10 @@ int res_create_alpha_surface(const char* name, GRSurface** pSurface) { return 0; } +void res_set_resource_dir(const std::string& dirname) { + g_resource_dir = dirname; +} + // This function tests if a locale string stored in PNG (prefix) matches // the locale string provided by the system (locale). bool matches_locale(const std::string& prefix, const std::string& locale) { diff --git a/otautil/include/otautil/paths.h b/otautil/include/otautil/paths.h index 788c3de3..39088f10 100644 --- a/otautil/include/otautil/paths.h +++ b/otautil/include/otautil/paths.h @@ -48,6 +48,13 @@ class Paths { last_command_file_ = last_command_file; } + std::string resource_dir() const { + return resource_dir_; + } + void set_resource_dir(const std::string& resource_dir) { + resource_dir_ = resource_dir; + } + std::string stash_directory_base() const { return stash_directory_base_; } @@ -85,6 +92,9 @@ class Paths { // Path to the last command file. std::string last_command_file_; + // Path to the resource dir; + std::string resource_dir_; + // Path to the base directory to write stashes during update. std::string stash_directory_base_; diff --git a/otautil/paths.cpp b/otautil/paths.cpp index ad9ec114..f08e51c7 100644 --- a/otautil/paths.cpp +++ b/otautil/paths.cpp @@ -19,6 +19,7 @@ constexpr const char kDefaultCacheLogDirectory[] = "/cache/recovery"; constexpr const char kDefaultCacheTempSource[] = "/cache/saved.file"; constexpr const char kDefaultLastCommandFile[] = "/cache/recovery/last_command"; +constexpr const char kDefaultResourceDirectory[] = "/res/images"; constexpr const char kDefaultStashDirectoryBase[] = "/cache/recovery"; constexpr const char kDefaultTemporaryInstallFile[] = "/tmp/last_install"; constexpr const char kDefaultTemporaryLogFile[] = "/tmp/recovery.log"; @@ -32,6 +33,7 @@ Paths::Paths() : cache_log_directory_(kDefaultCacheLogDirectory), cache_temp_source_(kDefaultCacheTempSource), last_command_file_(kDefaultLastCommandFile), + resource_dir_(kDefaultResourceDirectory), stash_directory_base_(kDefaultStashDirectoryBase), temporary_install_file_(kDefaultTemporaryInstallFile), temporary_log_file_(kDefaultTemporaryLogFile) {} diff --git a/screen_ui.cpp b/screen_ui.cpp index 90e0e30a..9198073d 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -41,9 +41,10 @@ #include #include #include -#include #include "device.h" +#include "minui/minui.h" +#include "otautil/paths.h" #include "ui.h" // Return the current time as a double (including fractions of a second). @@ -754,7 +755,8 @@ std::string ScreenRecoveryUI::GetLocale() { } void ScreenRecoveryUI::LoadAnimation() { - std::unique_ptr dir(opendir("/res/images"), closedir); + std::unique_ptr dir(opendir(Paths::Get().resource_dir().c_str()), + closedir); dirent* de; std::vector intro_frame_names; std::vector loop_frame_names; diff --git a/tests/Android.mk b/tests/Android.mk index 538ae63e..7234b521 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -27,6 +27,7 @@ LOCAL_STATIC_LIBRARIES := \ libminui \ libotautil \ libupdater \ + libpng \ libziparchive \ libutils \ libz \ diff --git a/tests/testdata/font.png b/tests/testdata/font.png new file mode 100644 index 0000000000000000000000000000000000000000..d95408a93dac15821ebdf2f20a4dcf51cd5b966b GIT binary patch literal 24437 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 iPODMT;D7$-e|`l`W4C*tt8`BQ0000 +#include +#include +#include #include #include +#include #include +#include "common/test_constants.h" +#include "device.h" +#include "otautil/paths.h" +#include "private/resources.h" #include "screen_ui.h" static const std::vector HEADERS{ "header" }; @@ -185,3 +193,162 @@ TEST(ScreenUITest, WearMenuSelectItemsOverflow) { 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 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& buffer); + + int KeyHandler(int key, bool visible) const; + + bool GetRtlLocale() const { + return rtl_locale_; + } + + private: + std::vector key_buffer_; + size_t key_buffer_index_; +}; + +void TestableScreenRecoveryUI::SetKeyBuffer(const std::vector& buffer) { + key_buffer_ = buffer; + key_buffer_index_ = 0; +} + +int TestableScreenRecoveryUI::KeyHandler(int key, bool) const { + KeyCode key_code = static_cast(key); + if (kKeyMapping.find(key_code) != kKeyMapping.end()) { + return kKeyMapping.at(key_code); + } + return Device::kNoAction; +} + +int TestableScreenRecoveryUI::WaitKey() { + CHECK_LT(key_buffer_index_, key_buffer_.size()); + return static_cast(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 { + ui_ = std::make_unique(); + + std::string testdata_dir = from_testdata_base(""); + Paths::Get().set_resource_dir(testdata_dir); + res_set_resource_dir(testdata_dir); + + ASSERT_TRUE(ui_->Init(kTestLocale)); + } + + std::unique_ptr ui_; +}; + +TEST_F(ScreenRecoveryUITest, Init) { + ASSERT_EQ(kTestLocale, ui_->GetLocale()); + ASSERT_FALSE(ui_->GetRtlLocale()); + ASSERT_FALSE(ui_->IsTextVisible()); + ASSERT_FALSE(ui_->WasTextEverVisible()); +} + +TEST_F(ScreenRecoveryUITest, ShowText) { + 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) { + ASSERT_TRUE(ui_->Init(kTestRtlLocale)); + ASSERT_TRUE(ui_->GetRtlLocale()); + + ASSERT_TRUE(ui_->Init(kTestRtlLocaleWithSuffix)); + ASSERT_TRUE(ui_->GetRtlLocale()); +} + +TEST_F(ScreenRecoveryUITest, ShowMenu) { + 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) { + ui_->SetKeyBuffer({ + KeyCode::MAGIC, + }); + ASSERT_EQ(static_cast(kMagicAction), + ui_->ShowMenu(HEADERS, ITEMS, 3, false, + std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(), + std::placeholders::_1, std::placeholders::_2))); +} + +TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut) { + ui_->SetKeyBuffer({ + KeyCode::TIMEOUT, + }); + ASSERT_EQ(static_cast(-1), ui_->ShowMenu(HEADERS, ITEMS, 3, true, nullptr)); +} + +TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) { + 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))); +}