Files
android_bootable_recovery/tests/unit/screen_ui_test.cpp
Tao Bao 51f16ec76d tests: Skip ScreenRecoveryUITest on gr_init failure.
It addresses the ScreenRecoveryUITest failures on gce targets which
don't have any graphics backend. Probing for all backend devices in
tests could work, but would duplicate codes. This CL relies on the
result of gr_init().

As a side effect, it may give false negatives if gr_init() is supposed
to work but silently broken. But such issues are beyond
ScreenRecoveryUITest's concern, which should be captured by the tests
for minui or graphics backends instead.

Fixes: 79616356
Test: Run recovery_unit_test on marlin.
Test: Run recovery_unit_test on gce.
Change-Id: I121aacc61c8a614447509506057ecfd8d86163e4
2018-06-13 23:28:21 -07:00

457 lines
13 KiB
C++

/*
* 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() {
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>(-1), 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, 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));
TemporaryDir resource_dir;
Paths::Get().set_resource_dir(resource_dir.path);
::testing::FLAGS_gtest_death_test_style = "threadsafe";
ASSERT_EXIT(ui_->RunLoadAnimation(), ::testing::KilledBySignal(SIGABRT), "");
}
#undef RETURN_IF_NO_GRAPHICS