blob: e730d2e3da092ec942e3512faade33d7f3ad5466 [file] [log] [blame]
// Copyright 2023 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 "input.h"
#include <lib/efi/testing/mock_serial_io.h>
#include <lib/efi/testing/mock_simple_text_input.h>
#include <lib/efi/testing/mock_simple_text_output.h>
#include <lib/efi/testing/stub_boot_services.h>
#include <gtest/gtest.h>
#include "gmock/gmock.h"
#include "lib/fit/internal/result.h"
namespace gigaboot {
const efi_handle kImageHandle = reinterpret_cast<efi_handle>(0x10);
// Test fixture to set up and tear down InputReceiver test state.
class InputReceiverTest : public ::testing::Test {
public:
// Reset xefi global variables so state doesn't bleed between tests.
void TearDown() override {
memset(&xefi_global_state, 0, sizeof(xefi_global_state));
gEfiSystemTable = nullptr;
gEfiImageHandle = nullptr;
}
// Sets up the state and mock expectations for a future call to xefi_init().
void SetupXefi(efi::MockBootServices& mock_services, efi_serial_io_protocol* serial,
efi_simple_text_input_protocol* text_input = nullptr,
efi_simple_text_output_protocol* text_output = nullptr) {
system_table_ = efi_system_table{
.ConIn = text_input,
.ConOut = text_output,
.BootServices = mock_services.services(),
};
gEfiSystemTable = &system_table_;
gEfiImageHandle = kImageHandle;
if (serial) {
EXPECT_CALL(mock_services, LocateProtocol(::efi::MatchGuid(EFI_SERIAL_IO_PROTOCOL_GUID),
::testing::_, ::testing::_))
.WillRepeatedly(::testing::DoAll(::testing::SetArgPointee<2>(serial),
::testing::Return(EFI_SUCCESS)));
EXPECT_CALL(mock_services,
CloseProtocol(::testing::_, ::efi::MatchGuid(EFI_SERIAL_IO_PROTOCOL_GUID),
kImageHandle, nullptr));
} else {
EXPECT_CALL(mock_services, LocateProtocol(::efi::MatchGuid(EFI_SERIAL_IO_PROTOCOL_GUID),
::testing::_, ::testing::_))
.WillRepeatedly(::testing::Return(EFI_LOAD_ERROR));
}
}
static InputReceiver::Serial& serial(InputReceiver& r) { return r.serial(); }
protected:
efi_system_table system_table_;
efi::MockBootServices mock_services_;
efi::MockSerialIoProtocol mock_serial_;
efi::MockSimpleTextInputProtocol mock_input_;
efi::MockSimpleTextOutputProtocol mock_output_{true};
char event_payload_;
};
TEST_F(InputReceiverTest, InitWithoutSerial) {
SetupXefi(mock_services_, nullptr);
InputReceiver receiver(&system_table_);
EXPECT_EQ(receiver.system_table(), &system_table_);
EXPECT_EQ(bool(serial(receiver)), false);
}
TEST_F(InputReceiverTest, InitWithSerial) {
SetupXefi(mock_services_, mock_serial_.protocol());
InputReceiver receiver(&system_table_);
EXPECT_EQ(receiver.system_table(), &system_table_);
EXPECT_EQ(bool(serial(receiver)), true);
EXPECT_CALL(mock_serial_, SetAttributes).WillOnce(::testing::Return(EFI_SUCCESS));
}
TEST_F(InputReceiverTest, GetKeySerialPoll) {
SetupXefi(mock_services_, mock_serial_.protocol(), mock_input_.protocol());
EXPECT_CALL(mock_serial_, SetAttributes).WillRepeatedly(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_input_, ReadKeyStroke).WillRepeatedly(::testing::Return(EFI_NOT_READY));
mock_serial_.ExpectRead("x");
InputReceiver receiver(&system_table_);
EXPECT_EQ('x', receiver.GetKey(zx::sec(0)));
}
TEST_F(InputReceiverTest, GetKeyInputPoll) {
SetupXefi(mock_services_, nullptr, mock_input_.protocol());
mock_input_.ExpectReadKeyStroke('z');
InputReceiver receiver(&system_table_);
EXPECT_EQ('z', receiver.GetKey(zx::sec(0)));
}
TEST_F(InputReceiverTest, GetKeyInputTakesPrecedence) {
SetupXefi(mock_services_, mock_serial_.protocol(), mock_input_.protocol());
EXPECT_CALL(mock_serial_, SetAttributes).WillRepeatedly(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_serial_, Read).Times(0);
mock_input_.ExpectReadKeyStroke('z');
InputReceiver receiver(&system_table_);
EXPECT_EQ('z', receiver.GetKey(zx::sec(0)));
}
TEST_F(InputReceiverTest, GetKeyPollNoCharacter) {
SetupXefi(mock_services_, mock_serial_.protocol(), mock_input_.protocol());
EXPECT_CALL(mock_serial_, SetAttributes).WillRepeatedly(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_serial_, Read).WillOnce(::testing::Return(EFI_TIMEOUT));
EXPECT_CALL(mock_input_, ReadKeyStroke).WillOnce(::testing::Return(EFI_NOT_READY));
InputReceiver receiver(&system_table_);
fit::result<efi_status, char> expected = fit::error(EFI_NOT_READY);
EXPECT_EQ(expected, receiver.GetKey(zx::sec(0)));
}
TEST_F(InputReceiverTest, GetKeyTimer) {
SetupXefi(mock_services_, mock_serial_.protocol(), mock_input_.protocol());
// Mock 3 "not ready" loops, then find a character on the 4th.
EXPECT_CALL(mock_services_, CreateEvent(EVT_TIMER, 0, nullptr, nullptr, ::testing::_))
.WillOnce(::testing::DoAll(::testing::SetArgPointee<4>(&event_payload_),
::testing::Return(EFI_SUCCESS)));
EXPECT_CALL(mock_services_, SetTimer(::testing::_, TimerRelative, ::testing::_))
.WillOnce(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_services_, CheckEvent(::testing::_))
.Times(3)
.WillRepeatedly(::testing::Return(EFI_NOT_READY));
EXPECT_CALL(mock_services_, CloseEvent(&event_payload_)).WillOnce(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_serial_, SetAttributes).WillRepeatedly(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_serial_, Read).Times(3).WillRepeatedly(::testing::Return(EFI_TIMEOUT));
{
::testing::InSequence seq;
EXPECT_CALL(mock_input_, ReadKeyStroke)
.Times(3)
.WillRepeatedly(::testing::Return(EFI_NOT_READY));
mock_input_.ExpectReadKeyStroke('z');
}
InputReceiver receiver(&system_table_);
EXPECT_EQ('z', receiver.GetKey(zx::sec(100)));
}
TEST_F(InputReceiverTest, GetKeyTimeout) {
SetupXefi(mock_services_, mock_serial_.protocol(), mock_input_.protocol());
// Mock 2 "not ready" loops, then timeout on the 3rd.
EXPECT_CALL(mock_services_, CreateEvent(EVT_TIMER, 0, nullptr, nullptr, ::testing::_))
.WillOnce(::testing::DoAll(::testing::SetArgPointee<4>(&event_payload_),
::testing::Return(EFI_SUCCESS)));
EXPECT_CALL(mock_services_, SetTimer(::testing::_, TimerRelative, ::testing::_))
.WillOnce(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_services_, CheckEvent(::testing::_))
.WillOnce(::testing::Return(EFI_NOT_READY))
.WillOnce(::testing::Return(EFI_NOT_READY))
.WillOnce(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_services_, CloseEvent(&event_payload_)).WillOnce(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_serial_, SetAttributes).WillRepeatedly(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_serial_, Read).Times(3).WillRepeatedly(::testing::Return(EFI_TIMEOUT));
EXPECT_CALL(mock_input_, ReadKeyStroke).Times(3).WillRepeatedly(::testing::Return(EFI_NOT_READY));
InputReceiver receiver(&system_table_);
fit::result<efi_status, char> expected = fit::error(EFI_NOT_READY);
EXPECT_EQ(expected, receiver.GetKey(zx::sec(100)));
}
TEST_F(InputReceiverTest, SerialAttributesFailure) {
SetupXefi(mock_services_, mock_serial_.protocol(), mock_input_.protocol());
EXPECT_CALL(mock_serial_, SetAttributes)
.WillOnce(::testing::Return(EFI_DEVICE_ERROR))
.WillOnce(::testing::Return(EFI_DEVICE_ERROR));
InputReceiver receiver(&system_table_);
fit::result<efi_status, char> expected = fit::error(EFI_DEVICE_ERROR);
EXPECT_EQ(expected, receiver.GetKey(zx::sec(0)));
}
TEST_F(InputReceiverTest, CreateTimerFailure) {
SetupXefi(mock_services_, mock_serial_.protocol(), mock_input_.protocol());
EXPECT_CALL(mock_serial_, SetAttributes).WillRepeatedly(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_services_, CreateEvent(EVT_TIMER, 0, nullptr, nullptr, ::testing::_))
.WillOnce(::testing::Return(EFI_OUT_OF_RESOURCES));
InputReceiver receiver(&system_table_);
fit::result<efi_status, char> expected = fit::error(EFI_OUT_OF_RESOURCES);
EXPECT_EQ(expected, receiver.GetKey(zx::sec(100)));
}
TEST_F(InputReceiverTest, SetTimerFailure) {
SetupXefi(mock_services_, mock_serial_.protocol(), mock_input_.protocol());
EXPECT_CALL(mock_serial_, SetAttributes).WillRepeatedly(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_services_, CreateEvent(EVT_TIMER, 0, nullptr, nullptr, ::testing::_))
.WillOnce(::testing::DoAll(::testing::SetArgPointee<4>(&event_payload_),
::testing::Return(EFI_SUCCESS)));
EXPECT_CALL(mock_services_, SetTimer(::testing::_, TimerRelative, ::testing::_))
.WillOnce(::testing::Return(EFI_INVALID_PARAMETER));
EXPECT_CALL(mock_services_, CloseEvent(&event_payload_)).WillOnce(::testing::Return(EFI_SUCCESS));
InputReceiver receiver(&system_table_);
fit::result<efi_status, char> expected = fit::error(EFI_INVALID_PARAMETER);
EXPECT_EQ(expected, receiver.GetKey(zx::sec(100)));
}
TEST_F(InputReceiverTest, GetKeyPrompt) {
SetupXefi(mock_services_, nullptr, mock_input_.protocol(), mock_output_.protocol());
{
::testing::InSequence s;
EXPECT_CALL(mock_output_, EnableCursor(false)).WillOnce(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_output_, EnableCursor(true)).WillOnce(::testing::Return(EFI_SUCCESS));
}
EXPECT_CALL(mock_services_, CreateEvent(EVT_TIMER, 0, nullptr, nullptr, ::testing::_))
.WillOnce(::testing::DoAll(::testing::SetArgPointee<4>(&event_payload_),
::testing::Return(EFI_SUCCESS)));
EXPECT_CALL(mock_services_, SetTimer(::testing::_, ::testing::_, ::testing::_))
.WillOnce(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_services_, CloseEvent(&event_payload_)).WillOnce(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_output_, SetCursorPosition(0, 0)).WillRepeatedly(::testing::Return(EFI_SUCCESS));
mock_input_.ExpectReadKeyStroke('f');
InputReceiver receiver(&system_table_);
EXPECT_EQ(receiver.GetKeyPrompt("f", zx::sec(1)), 'f');
}
TEST_F(InputReceiverTest, GetKeyPromptWrongKey) {
SetupXefi(mock_services_, nullptr, mock_input_.protocol(), mock_output_.protocol());
{
::testing::InSequence s;
EXPECT_CALL(mock_output_, EnableCursor(false)).WillOnce(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_output_, EnableCursor(true)).WillOnce(::testing::Return(EFI_SUCCESS));
}
EXPECT_CALL(mock_services_, CreateEvent(EVT_TIMER, 0, nullptr, nullptr, ::testing::_))
.WillRepeatedly(::testing::DoAll(::testing::SetArgPointee<4>(&event_payload_),
::testing::Return(EFI_SUCCESS)));
EXPECT_CALL(mock_services_, CloseEvent(&event_payload_))
.WillRepeatedly(::testing::Return(EFI_SUCCESS));
mock_input_.ExpectRepeatedReadKeyStroke('z');
EXPECT_CALL(mock_services_, CheckEvent(::testing::_))
.WillRepeatedly(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_services_, SetTimer(::testing::_, ::testing::_, ::testing::_))
.WillRepeatedly(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_output_, SetCursorPosition(0, 0)).WillRepeatedly(::testing::Return(EFI_SUCCESS));
InputReceiver receiver(&system_table_);
EXPECT_EQ(receiver.GetKeyPrompt("f", zx::sec(5)), std::nullopt);
}
TEST_F(InputReceiverTest, GetKeyPromptTimeout) {
SetupXefi(mock_services_, nullptr, mock_input_.protocol(), mock_output_.protocol());
{
::testing::InSequence s;
EXPECT_CALL(mock_output_, EnableCursor(false)).WillOnce(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_output_, EnableCursor(true)).WillOnce(::testing::Return(EFI_SUCCESS));
}
EXPECT_CALL(mock_input_, ReadKeyStroke).WillRepeatedly(::testing::Return(EFI_NOT_READY));
EXPECT_CALL(mock_services_, CreateEvent(EVT_TIMER, 0, nullptr, nullptr, ::testing::_))
.WillRepeatedly(::testing::DoAll(::testing::SetArgPointee<4>(&event_payload_),
::testing::Return(EFI_SUCCESS)));
EXPECT_CALL(mock_services_, CheckEvent(&event_payload_))
.WillRepeatedly(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_services_, SetTimer(::testing::_, ::testing::_, ::testing::_))
.WillRepeatedly(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_services_, CloseEvent(&event_payload_))
.WillRepeatedly(::testing::Return(EFI_SUCCESS));
EXPECT_CALL(mock_output_, SetCursorPosition(0, 0)).WillRepeatedly(::testing::Return(EFI_SUCCESS));
InputReceiver receiver(&system_table_);
EXPECT_EQ(receiver.GetKeyPrompt("f", zx::sec(5)), std::nullopt);
}
} // namespace gigaboot