blob: 6fc021d9be4ad8356e0ae20c95a032efa595f3d2 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "osboot.h"
#include <lib/efi/testing/mock_simple_text_input.h>
#include <lib/efi/testing/stub_boot_services.h>
#include <efi/protocol/simple-text-output.h>
#include <gtest/gtest.h>
#include "bootbyte.h"
#include "cmdline.h"
#include "xefi.h"
namespace {
using ::efi::MatchGuid;
using ::efi::MockBootServices;
using ::efi::MockSimpleTextInputProtocol;
using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::Test;
const efi_handle kImageHandle = reinterpret_cast<efi_handle>(0x10);
const efi_event kTimerEvent = reinterpret_cast<efi_event>(0x80);
// If none of bootbyte, menu, or "bootloader.default" are provided, current
// default is netboot. See if we can switch this to local boot default?
constexpr BootAction kFallthroughBootAction = kBootActionNetboot;
// We don't have efi_simple_test_output_protocol mocks hooked up yet, for now
// just stub them out for simplicity since that's all we need.
EFIAPI efi_status StubEnableCursor(struct efi_simple_text_output_protocol*, bool) {
return EFI_SUCCESS;
}
EFIAPI efi_status StubSetCursorPosition(struct efi_simple_text_output_protocol*, size_t, size_t) {
return EFI_SUCCESS;
}
class GetBootActionTest : public Test {
public:
void SetUp() override {
// Configure the necessary mocks for key_prompt().
system_table_ = efi_system_table{
.ConIn = mock_input_.protocol(),
.ConOut = &output_protocol_,
.BootServices = mock_services_.services(),
};
output_protocol_ = efi_simple_text_output_protocol{.SetCursorPosition = StubSetCursorPosition,
.EnableCursor = StubEnableCursor,
.Mode = &output_mode_};
// Just use console in, no need for serial.
EXPECT_CALL(mock_services_, LocateProtocol(MatchGuid(EFI_SERIAL_IO_PROTOCOL_GUID), _, _))
.WillOnce(Return(EFI_LOAD_ERROR));
xefi_init(kImageHandle, &system_table_);
// Default behavior is to timeout without a key input.
cmdline_set("bootloader.timeout", "1");
ON_CALL(mock_input_, ReadKeyStroke).WillByDefault(Return(EFI_NOT_READY));
ON_CALL(mock_services_, CreateEvent(EVT_TIMER, _, _, _, _))
.WillByDefault([](uint32_t, efi_tpl, efi_event_notify, void*, efi_event* event) {
// This doesn't have to point to real memory, but it has to be
// non-NULL to make it look like the call succeeded.
*event = kTimerEvent;
return EFI_SUCCESS;
});
ON_CALL(mock_services_, CheckEvent(kTimerEvent)).WillByDefault(Return(EFI_SUCCESS));
}
void TearDown() override {
// Reset all used state in between each test.
bootbyte_clear();
memset(&xefi_global_state, 0, sizeof(xefi_global_state));
cmdline_clear();
}
void SetUserInput(char key) { mock_input_.ExpectReadKeyStroke(key); }
protected:
NiceMock<MockBootServices> mock_services_;
NiceMock<MockSimpleTextInputProtocol> mock_input_;
efi_simple_text_output_protocol output_protocol_ = {};
simple_text_output_mode output_mode_ = {};
efi_system_table system_table_ = {};
};
TEST_F(GetBootActionTest, BootbyteRecovery) {
bootbyte_set_recovery();
EXPECT_EQ(kBootActionSlotR, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, BootbyteBootloader) {
bootbyte_set_bootloader();
EXPECT_EQ(kBootActionFastboot, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, BootbyteNormal) {
bootbyte_set_normal();
EXPECT_EQ(kFallthroughBootAction, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, BootbyteDefault) {
bootbyte_clear();
EXPECT_EQ(kFallthroughBootAction, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, BootbyteWithCount) {
// Make sure we ignore the count value in the upper bits.
bootbyte_set_for_test(0x30 | RTC_BOOT_BOOTLOADER);
EXPECT_EQ(kBootActionFastboot, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, MenuSelectA) {
SetUserInput('1');
EXPECT_EQ(kBootActionSlotA, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, MenuSelectB) {
SetUserInput('2');
EXPECT_EQ(kBootActionSlotB, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, MenuSelectRecovery) {
SetUserInput('r');
EXPECT_EQ(kBootActionSlotR, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, MenuSelectFastboot) {
SetUserInput('f');
EXPECT_EQ(kBootActionFastboot, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, MenuSelectDfv2) {
const char* input = "d1";
mock_input_.ExpectReadKeyStrokes(&input);
bool use_dfv2 = false;
EXPECT_EQ(kBootActionSlotA, get_boot_action(true, true, &use_dfv2));
EXPECT_TRUE(use_dfv2);
}
TEST_F(GetBootActionTest, MenuSelectNetboot) {
SetUserInput('n');
EXPECT_EQ(kBootActionNetboot, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, MenuSelectNetbootRequiresNetwork) {
// If user tries to select "n" without a network, we should fall through
// to whatever the bootloader.default commandline arg has.
cmdline_set("bootloader.default", "local");
SetUserInput('n');
EXPECT_EQ(kBootActionDefault, get_boot_action(false, true, nullptr));
}
TEST_F(GetBootActionTest, CommandlineLocal) {
cmdline_set("bootloader.default", "local");
EXPECT_EQ(kBootActionDefault, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, CommandlineNetwork) {
cmdline_set("bootloader.default", "network");
EXPECT_EQ(kBootActionNetboot, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, CommandlineNetworkRequiresNetwork) {
// If commandline tries to select network but isn't connected, we should fall
// back to a boot from disk.
cmdline_set("bootloader.default", "network");
EXPECT_EQ(kBootActionDefault, get_boot_action(false, true, nullptr));
}
TEST_F(GetBootActionTest, CommandlineZedboot) {
cmdline_set("bootloader.default", "zedboot");
EXPECT_EQ(kBootActionSlotR, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, CommandlineUnknown) {
// If "bootloader.default" is an unknown value, default to local.
cmdline_set("bootloader.default", "foo");
EXPECT_EQ(kBootActionDefault, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, CommandlineDefault) {
EXPECT_EQ(kFallthroughBootAction, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, CommandlineDefaultRequiresNetwork) {
// We only need this while the default action is a netboot, if we change
// to default to a local boot this test can be deleted.
static_assert(kBootActionNetboot == kFallthroughBootAction, "Delete this test");
// If network is unavailable we should fall back to a boot from disk
// (required for GCE).
EXPECT_EQ(kBootActionDefault, get_boot_action(false, true, nullptr));
}
TEST_F(GetBootActionTest, BootbyteFirst) {
// Make sure the bootbyte is given priority if all are set.
bootbyte_set_bootloader();
EXPECT_CALL(mock_input_, ReadKeyStroke).Times(0);
cmdline_set("bootloader.default", "local");
EXPECT_EQ(kBootActionFastboot, get_boot_action(true, true, nullptr));
}
TEST_F(GetBootActionTest, MenuSelectSecond) {
// Make the user menu is given priority over the commandline.
SetUserInput('f');
cmdline_set("bootloader.default", "local");
EXPECT_EQ(kBootActionFastboot, get_boot_action(true, true, nullptr));
}
} // namespace