| /* Copyright 2020 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| * |
| * Tests for UI utility functions. |
| */ |
| |
| #include "2api.h" |
| #include "2common.h" |
| #include "2misc.h" |
| #include "2nvstorage.h" |
| #include "2ui.h" |
| #include "2ui_private.h" |
| #include "test_common.h" |
| #include "vboot_api.h" |
| |
| /* Fixed value for ignoring some checks. */ |
| #define MOCK_IGNORE 0xffffu |
| |
| /* Mock screen index for testing screen utility functions. */ |
| #define MOCK_NO_SCREEN 0xef00 |
| #define MOCK_SCREEN_BASE 0xef10 |
| #define MOCK_SCREEN_MENU 0xef11 |
| #define MOCK_SCREEN_ROOT 0xefff |
| |
| /* Mock data */ |
| static uint8_t workbuf[VB2_KERNEL_WORKBUF_RECOMMENDED_SIZE] |
| __attribute__((aligned(VB2_WORKBUF_ALIGN))); |
| static struct vb2_context *ctx; |
| static struct vb2_gbb_header gbb; |
| |
| static uint32_t mock_locale_count; |
| static int mock_shutdown_request; |
| |
| static struct vb2_ui_context mock_ui_context; |
| |
| /* Mock actions */ |
| static uint32_t mock_action_called; |
| static vb2_error_t mock_action_base(struct vb2_ui_context *ui) |
| { |
| mock_action_called++; |
| return VB2_SUCCESS; |
| } |
| |
| /* Mock screens */ |
| struct vb2_screen_info mock_screen_blank = { |
| .id = VB2_SCREEN_BLANK, |
| .name = "mock_screen_blank", |
| }; |
| struct vb2_screen_info mock_screen_base = { |
| .id = MOCK_SCREEN_BASE, |
| .name = "mock_screen_base: menuless screen", |
| }; |
| struct vb2_menu_item mock_screen_menu_items[] = { |
| { |
| .text = "option 0: language selection", |
| .is_language_select = 1, |
| }, |
| { |
| .text = "option 1", |
| }, |
| { |
| .text = "option 2", |
| }, |
| { |
| .text = "option 3", |
| }, |
| { |
| .text = "option 4", |
| }, |
| }; |
| struct vb2_screen_info mock_screen_menu = { |
| .id = MOCK_SCREEN_MENU, |
| .name = "mock_screen_menu: screen with 5 options", |
| .menu = { |
| .num_items = ARRAY_SIZE(mock_screen_menu_items), |
| .items = mock_screen_menu_items, |
| }, |
| }; |
| struct vb2_screen_info mock_screen_root = { |
| .id = MOCK_SCREEN_ROOT, |
| .name = "mock_screen_root", |
| }; |
| |
| static void screen_state_eq(const struct vb2_screen_state *state, |
| enum vb2_screen screen, |
| uint32_t selected_item, |
| uint32_t hidden_item_mask) |
| { |
| if (screen != MOCK_IGNORE) { |
| if (state->screen == NULL) |
| TEST_TRUE(0, " state.screen does not exist"); |
| else |
| TEST_EQ(state->screen->id, screen, " state.screen"); |
| } |
| if (selected_item != MOCK_IGNORE) |
| TEST_EQ(state->selected_item, |
| selected_item, " state.selected_item"); |
| if (hidden_item_mask != MOCK_IGNORE) |
| TEST_EQ(state->hidden_item_mask, |
| hidden_item_mask, " state.hidden_item_mask"); |
| } |
| |
| /* Reset mock data (for use before each test) */ |
| static void reset_common_data(void) |
| { |
| TEST_SUCC(vb2api_init(workbuf, sizeof(workbuf), &ctx), |
| "vb2api_init failed"); |
| |
| memset(&gbb, 0, sizeof(gbb)); |
| |
| vb2_nv_init(ctx); |
| |
| /* For vb2ex_get_locale_count */ |
| mock_locale_count = 1; |
| |
| /* For check_shutdown_request */ |
| mock_shutdown_request = MOCK_IGNORE; |
| |
| /* Mock ui_context based on mock screens */ |
| memset(&mock_ui_context, 0, sizeof(mock_ui_context)); |
| mock_ui_context.power_button = VB2_POWER_BUTTON_HELD_SINCE_BOOT; |
| |
| /* For mock actions */ |
| mock_action_called = 0; |
| |
| /* Reset init and action functions */ |
| mock_screen_blank.init = NULL; |
| mock_screen_blank.action = NULL; |
| mock_screen_base.init = NULL; |
| mock_screen_base.action = NULL; |
| mock_screen_menu.init = NULL; |
| mock_screen_menu.action = NULL; |
| mock_screen_root.init = NULL; |
| mock_screen_root.action = NULL; |
| } |
| |
| /* Mock functions */ |
| struct vb2_gbb_header *vb2_get_gbb(struct vb2_context *c) |
| { |
| return &gbb; |
| } |
| |
| uint32_t vb2ex_get_locale_count(void) |
| { |
| return mock_locale_count; |
| } |
| |
| uint32_t VbExIsShutdownRequested(void) |
| { |
| if (mock_shutdown_request != MOCK_IGNORE) |
| return mock_shutdown_request; |
| |
| return 0; |
| } |
| |
| const struct vb2_screen_info *vb2_get_screen_info(enum vb2_screen screen) |
| { |
| switch ((int)screen) { |
| case VB2_SCREEN_BLANK: |
| return &mock_screen_blank; |
| case MOCK_SCREEN_BASE: |
| return &mock_screen_base; |
| case MOCK_SCREEN_MENU: |
| return &mock_screen_menu; |
| case MOCK_SCREEN_ROOT: |
| return &mock_screen_root; |
| default: |
| return NULL; |
| } |
| } |
| |
| /* Tests */ |
| static void check_shutdown_request_tests(void) |
| { |
| VB2_DEBUG("Testing check_shutdown_request...\n"); |
| |
| /* Release, press, hold, and release */ |
| if (!DETACHABLE) { |
| reset_common_data(); |
| mock_shutdown_request = 0; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_UI_CONTINUE, |
| "release, press, hold, and release"); |
| mock_shutdown_request = VB_SHUTDOWN_REQUEST_POWER_BUTTON; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_UI_CONTINUE, " press"); |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_UI_CONTINUE, " hold"); |
| mock_shutdown_request = 0; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_SHUTDOWN, " release"); |
| } |
| |
| /* Press is ignored because we may held since boot */ |
| if (!DETACHABLE) { |
| reset_common_data(); |
| mock_shutdown_request = VB_SHUTDOWN_REQUEST_POWER_BUTTON; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_UI_CONTINUE, "press is ignored"); |
| } |
| |
| /* Power button short press from key */ |
| if (!DETACHABLE) { |
| reset_common_data(); |
| mock_shutdown_request = 0; |
| mock_ui_context.key = VB_BUTTON_POWER_SHORT_PRESS; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_SHUTDOWN, "power button short press"); |
| } |
| |
| /* Lid closure = shutdown request anyway */ |
| reset_common_data(); |
| mock_shutdown_request = VB_SHUTDOWN_REQUEST_LID_CLOSED; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_SHUTDOWN, "lid closure"); |
| mock_ui_context.key = 'A'; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_SHUTDOWN, " lidsw + random key"); |
| |
| /* Lid ignored by GBB flags */ |
| reset_common_data(); |
| gbb.flags |= VB2_GBB_FLAG_DISABLE_LID_SHUTDOWN; |
| mock_shutdown_request = VB_SHUTDOWN_REQUEST_LID_CLOSED; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_UI_CONTINUE, "lid ignored"); |
| if (!DETACHABLE) { /* Power button works for non DETACHABLE */ |
| mock_shutdown_request = VB_SHUTDOWN_REQUEST_LID_CLOSED | |
| VB_SHUTDOWN_REQUEST_POWER_BUTTON; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_UI_CONTINUE, " lidsw + pwdsw"); |
| mock_shutdown_request = 0; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_SHUTDOWN, " pwdsw release"); |
| } |
| |
| /* Lid ignored; power button short pressed */ |
| if (!DETACHABLE) { |
| reset_common_data(); |
| gbb.flags |= VB2_GBB_FLAG_DISABLE_LID_SHUTDOWN; |
| mock_shutdown_request = VB_SHUTDOWN_REQUEST_LID_CLOSED; |
| mock_ui_context.key = VB_BUTTON_POWER_SHORT_PRESS; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_SHUTDOWN, |
| "lid ignored; power button short pressed"); |
| } |
| |
| /* DETACHABLE ignore power button */ |
| if (DETACHABLE) { |
| /* Flag pwdsw */ |
| reset_common_data(); |
| mock_shutdown_request = VB_SHUTDOWN_REQUEST_POWER_BUTTON; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_UI_CONTINUE, "DETACHABLE: ignore pwdsw"); |
| mock_shutdown_request = 0; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_UI_CONTINUE, " ignore on release"); |
| |
| /* Power button short press */ |
| reset_common_data(); |
| mock_shutdown_request = 0; |
| mock_ui_context.key = VB_BUTTON_POWER_SHORT_PRESS; |
| TEST_EQ(check_shutdown_request(&mock_ui_context), |
| VB2_REQUEST_UI_CONTINUE, |
| "DETACHABLE: ignore power button short press"); |
| } |
| |
| VB2_DEBUG("...done.\n"); |
| } |
| |
| static void screen_stack_tests(void) |
| { |
| VB2_DEBUG("Testing screen stack functionality...\n"); |
| |
| /* Change to screen which does not exist */ |
| reset_common_data(); |
| TEST_EQ(vb2_ui_screen_change(&mock_ui_context, MOCK_NO_SCREEN), |
| VB2_REQUEST_UI_CONTINUE, |
| "change to screen which does not exist"); |
| TEST_PTR_EQ(mock_ui_context.state, NULL, " stack is empty"); |
| |
| /* Screen back with empty stack */ |
| reset_common_data(); |
| TEST_EQ(vb2_ui_screen_back(&mock_ui_context), |
| VB2_REQUEST_UI_CONTINUE, |
| "screen back with empty stack"); |
| TEST_PTR_EQ(mock_ui_context.state, NULL, " stack is empty"); |
| |
| /* Back to previous screen, restoring the state */ |
| reset_common_data(); |
| mock_screen_base.init = mock_action_base; |
| vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_ROOT); |
| vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_BASE); |
| mock_ui_context.state->selected_item = 2; |
| mock_ui_context.state->hidden_item_mask = 0x10; |
| vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_MENU); |
| TEST_EQ(vb2_ui_screen_back(&mock_ui_context), VB2_REQUEST_UI_CONTINUE, |
| "back to previous screen"); |
| screen_state_eq(mock_ui_context.state, MOCK_SCREEN_BASE, 2, 0x10); |
| TEST_EQ(mock_action_called, 1, " action called once"); |
| |
| /* Change to target screen already in stack, restoring the state */ |
| reset_common_data(); |
| mock_screen_base.init = mock_action_base; |
| vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_ROOT); |
| vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_BASE); |
| mock_ui_context.state->selected_item = 2; |
| mock_ui_context.state->hidden_item_mask = 0x10; |
| vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_MENU); |
| TEST_EQ(vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_BASE), |
| VB2_REQUEST_UI_CONTINUE, "change to target in stack"); |
| screen_state_eq(mock_ui_context.state, MOCK_SCREEN_BASE, 2, 0x10); |
| TEST_EQ(mock_action_called, 1, " action called once"); |
| |
| /* Change to screen without init; using default init() */ |
| reset_common_data(); |
| TEST_EQ(vb2_ui_screen_change(&mock_ui_context, MOCK_SCREEN_MENU), |
| VB2_REQUEST_UI_CONTINUE, |
| "change to screen with language selection"); |
| screen_state_eq(mock_ui_context.state, MOCK_SCREEN_MENU, |
| 1, /* Since index 0 is the language selection */ |
| 0); |
| |
| VB2_DEBUG("...done.\n"); |
| } |
| |
| static void get_language_menu_tests(void) |
| { |
| const struct vb2_menu *menu; |
| const struct vb2_menu_item *items; |
| VB2_DEBUG("Testing get_language_menu...\n"); |
| |
| /* Only allocate menu items once */ |
| reset_common_data(); |
| mock_locale_count = 7; |
| menu = get_language_menu(&mock_ui_context); |
| TEST_PTR_NEQ(menu, NULL, "get language menu"); |
| TEST_EQ(menu->num_items, 7, " correct locale count"); |
| TEST_PTR_NEQ(menu->items, NULL, " items not null"); |
| items = menu->items; |
| |
| menu = get_language_menu(&mock_ui_context); |
| TEST_PTR_NEQ(menu, NULL, "get language menu again"); |
| TEST_EQ(menu->num_items, 7, " correct locale count again"); |
| TEST_PTR_EQ(menu->items, items, " same pointer of items"); |
| |
| /* Locale count = 0 */ |
| reset_common_data(); |
| mock_locale_count = 0; |
| menu = get_language_menu(&mock_ui_context); |
| TEST_PTR_NEQ(menu, NULL, "menu not null"); |
| TEST_EQ(menu->num_items, 1, " locale count 1"); |
| |
| VB2_DEBUG("...done.\n"); |
| } |
| |
| int main(void) |
| { |
| check_shutdown_request_tests(); |
| screen_stack_tests(); |
| get_language_menu_tests(); |
| |
| return gTestSuccess ? 0 : 255; |
| } |