| // Copyright 2019 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "libec/ec_command_async.h" | |
| #include <gmock/gmock.h> | |
| #include <gtest/gtest.h> | |
| using testing::_; | |
| using testing::InvokeWithoutArgs; | |
| using testing::Return; | |
| namespace ec { | |
| namespace { | |
| constexpr int kDummyFd = 0; | |
| constexpr int kIoctlFailureRetVal = -1; | |
| template <typename O, typename I> | |
| class MockEcCommandAsync : public EcCommandAsync<O, I> { | |
| public: | |
| using EcCommandAsync<O, I>::EcCommandAsync; | |
| ~MockEcCommandAsync() override = default; | |
| using Data = typename EcCommandAsync<O, I>::Data; | |
| MOCK_METHOD(int, ioctl, (int fd, uint32_t request, Data* data), (override)); | |
| }; | |
| class MockAddEntropyCommand | |
| : public MockEcCommandAsync<struct ec_params_rollback_add_entropy, | |
| EmptyParam> { | |
| public: | |
| explicit MockAddEntropyCommand(const Options& options) | |
| : MockEcCommandAsync( | |
| EC_CMD_ADD_ENTROPY, ADD_ENTROPY_GET_RESULT, options) {} | |
| static constexpr std::size_t expected_response_size = 0; | |
| }; | |
| class MockFlashProtectCommand | |
| : public MockEcCommandAsync<struct ec_params_flash_protect_v2, | |
| struct ec_response_flash_protect> { | |
| public: | |
| explicit MockFlashProtectCommand(const Options& options) | |
| : MockEcCommandAsync( | |
| EC_CMD_FLASH_PROTECT, FLASH_PROTECT_GET_RESULT, options) {} | |
| static constexpr std::size_t expected_response_size = | |
| sizeof(ec_response_flash_protect); | |
| }; | |
| template <typename T> | |
| class EcCommandAsyncTest : public testing::Test {}; | |
| using EcCommandAsyncTestTypes = | |
| ::testing::Types<MockAddEntropyCommand, MockFlashProtectCommand>; | |
| TYPED_TEST_SUITE(EcCommandAsyncTest, EcCommandAsyncTestTypes); | |
| // ioctl behavior for EC commands: | |
| // returns sizeof(EC response) (>=0) on success, -1 on failure | |
| // cmd.result is error code from EC (EC_RES_SUCCESS, etc) | |
| TYPED_TEST(EcCommandAsyncTest, Run_Success) { | |
| TypeParam mock_cmd({.poll_for_result_num_attempts = 2, | |
| .poll_interval = base::Milliseconds(1)}); | |
| EXPECT_CALL(mock_cmd, ioctl) | |
| .Times(3) | |
| // First call to ioctl() to start the command; EC returns success. | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| data->cmd.result = EC_RES_SUCCESS; | |
| EXPECT_EQ(data->cmd.insize, 0); | |
| return data->cmd.insize; | |
| }) | |
| // Second call to ioctl() to get the result; EC returns busy. | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| data->cmd.result = EC_RES_BUSY; | |
| EXPECT_EQ(data->cmd.insize, TypeParam::expected_response_size); | |
| return data->cmd.insize; | |
| }) | |
| // Third call to ioctl() to get the result; EC returns success. | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| data->cmd.result = EC_RES_SUCCESS; | |
| EXPECT_EQ(data->cmd.insize, TypeParam::expected_response_size); | |
| return data->cmd.insize; | |
| }); | |
| EXPECT_TRUE(mock_cmd.Run(kDummyFd)); | |
| EXPECT_EQ(mock_cmd.Result(), EC_RES_SUCCESS); | |
| } | |
| TYPED_TEST(EcCommandAsyncTest, Run_TimeoutFailure) { | |
| TypeParam mock_cmd({.poll_for_result_num_attempts = 2, | |
| .poll_interval = base::Milliseconds(1)}); | |
| EXPECT_CALL(mock_cmd, ioctl) | |
| .Times(3) | |
| // First call to ioctl() to start the command; EC returns success. | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| data->cmd.result = EC_RES_SUCCESS; | |
| EXPECT_EQ(data->cmd.insize, 0); | |
| return data->cmd.insize; | |
| }) | |
| // All remaining ioctl() calls; EC returns busy. | |
| .WillRepeatedly([](int, uint32_t, typename TypeParam::Data* data) { | |
| data->cmd.result = EC_RES_BUSY; | |
| EXPECT_EQ(data->cmd.insize, TypeParam::expected_response_size); | |
| return data->cmd.insize; | |
| }); | |
| EXPECT_FALSE(mock_cmd.Run(kDummyFd)); | |
| EXPECT_EQ(mock_cmd.Result(), EC_RES_BUSY); | |
| } | |
| TYPED_TEST(EcCommandAsyncTest, Run_Failure) { | |
| TypeParam mock_cmd({// With the number of attempts set to 2, there will be at | |
| // most 3 ioctl calls (the extra one starts the command). | |
| // In this test case, we're validating that the last | |
| // ioctl() call will not be performed because we got an | |
| // error on the second ioctl() call. | |
| .poll_for_result_num_attempts = 2, | |
| .poll_interval = base::Milliseconds(1)}); | |
| EXPECT_CALL(mock_cmd, ioctl) | |
| .Times(2) | |
| // First call to ioctl() to start the command; EC returns success. | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| data->cmd.result = EC_RES_SUCCESS; | |
| EXPECT_EQ(data->cmd.insize, 0); | |
| return data->cmd.insize; | |
| }) | |
| // Second call to ioctl() to get the result; EC returns error. | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| data->cmd.result = EC_RES_ERROR; | |
| EXPECT_EQ(data->cmd.insize, TypeParam::expected_response_size); | |
| return data->cmd.insize; | |
| }); | |
| EXPECT_FALSE(mock_cmd.Run(kDummyFd)); | |
| EXPECT_EQ(mock_cmd.Result(), EC_RES_ERROR); | |
| } | |
| TYPED_TEST(EcCommandAsyncTest, Run_IoctlTimesOut) { | |
| TypeParam mock({ | |
| // With the number of attempts set to 2, there will be at | |
| // most 3 ioctl calls (the extra one starts the command). In | |
| // this test case, we're validating that the last ioctl() | |
| // call will not be performed because we got an error on | |
| // the second ioctl() call. | |
| .poll_for_result_num_attempts = 2, | |
| .poll_interval = base::Milliseconds(1), | |
| }); | |
| EXPECT_CALL(mock, ioctl) | |
| .Times(2) | |
| // First call to ioctl() to start the command; EC returns success. | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| data->cmd.result = EC_RES_SUCCESS; | |
| EXPECT_EQ(data->cmd.insize, 0); | |
| return data->cmd.insize; | |
| }) | |
| // Second call to ioctl() to get the result returns error (EC not | |
| // responding). | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| errno = ETIMEDOUT; | |
| EXPECT_EQ(data->cmd.insize, TypeParam::expected_response_size); | |
| return kIoctlFailureRetVal; | |
| }); | |
| EXPECT_FALSE(mock.Run(kDummyFd)); | |
| EXPECT_EQ(mock.Result(), kEcCommandUninitializedResult); | |
| } | |
| TYPED_TEST(EcCommandAsyncTest, Run_IoctlTimesOut_IgnoreFailure) { | |
| TypeParam mock({.poll_for_result_num_attempts = 2, | |
| .poll_interval = base::Milliseconds(1), | |
| .validate_poll_result = false}); | |
| EXPECT_CALL(mock, ioctl) | |
| .Times(3) | |
| // First call to ioctl() to start the command; EC returns success. | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| data->cmd.result = EC_RES_SUCCESS; | |
| EXPECT_EQ(data->cmd.insize, 0); | |
| return data->cmd.insize; | |
| }) | |
| // Second call to ioctl() to get the result returns error; EC not | |
| // responding. | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| errno = ETIMEDOUT; | |
| EXPECT_EQ(data->cmd.insize, TypeParam::expected_response_size); | |
| return kIoctlFailureRetVal; | |
| }) | |
| // Third call to ioctl() to get the result; EC returns success. | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| data->cmd.result = EC_RES_SUCCESS; | |
| EXPECT_EQ(data->cmd.insize, TypeParam::expected_response_size); | |
| return data->cmd.insize; | |
| }); | |
| EXPECT_TRUE(mock.Run(kDummyFd)); | |
| EXPECT_EQ(mock.Result(), EC_RES_SUCCESS); | |
| } | |
| TYPED_TEST(EcCommandAsyncTest, Run_InvalidOptions_ZeroPollAttempts) { | |
| TypeParam mock({.poll_for_result_num_attempts = 0}); | |
| EXPECT_DEATH(mock.Run(kDummyFd), "poll_for_result_num_attempts > 0"); | |
| } | |
| TYPED_TEST(EcCommandAsyncTest, Run_InvalidOptions_NegativePollAttempts) { | |
| TypeParam mock({.poll_for_result_num_attempts = -1}); | |
| EXPECT_DEATH(mock.Run(kDummyFd), "poll_for_result_num_attempts > 0"); | |
| } | |
| TYPED_TEST(EcCommandAsyncTest, DefaultOptions) { | |
| typename TypeParam::Options options; | |
| EXPECT_EQ(options.validate_poll_result, true); | |
| EXPECT_EQ(options.poll_for_result_num_attempts, 20); | |
| EXPECT_EQ(options.poll_interval, base::Milliseconds(100)); | |
| } | |
| // It's possible for the implementation of the command to incorrectly return | |
| // the wrong size. The kernel driver does not check for this, but Run() should | |
| // return an error since the data returned is not what was requested. | |
| TYPED_TEST(EcCommandAsyncTest, Run_SecondBaseCmdResponseSizeLarge) { | |
| TypeParam mock_cmd({// With the number of attempts set to 2, there will be at | |
| // most 3 ioctl calls (the extra one starts the command). | |
| // In this test case, we're validating that the last | |
| // ioctl() call will not be performed because we got a | |
| // success on the second ioctl() call. | |
| .poll_for_result_num_attempts = 2, | |
| .poll_interval = base::Milliseconds(1)}); | |
| EXPECT_CALL(mock_cmd, ioctl) | |
| .Times(2) | |
| // First call to ioctl() to start the command; EC returns success. | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| data->cmd.result = EC_RES_SUCCESS; | |
| EXPECT_EQ(data->cmd.insize, 0); | |
| return data->cmd.insize; | |
| }) | |
| // Second call to ioctl() to get the result; EC returns success. However, | |
| // the size is different from the expected command size. | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| data->cmd.result = EC_RES_SUCCESS; | |
| return data->cmd.insize + 1; | |
| }); | |
| EXPECT_FALSE(mock_cmd.Run(kDummyFd)); | |
| } | |
| TYPED_TEST(EcCommandAsyncTest, Run_FirstBaseCmdFail) { | |
| TypeParam mock_cmd({.poll_for_result_num_attempts = 2, | |
| .poll_interval = base::Milliseconds(1)}); | |
| EXPECT_CALL(mock_cmd, ioctl) | |
| .Times(1) | |
| // First call to ioctl() to start the command; EC returns error. | |
| .WillOnce([](int, uint32_t, typename TypeParam::Data* data) { | |
| data->cmd.result = EC_RES_ERROR; | |
| EXPECT_EQ(data->cmd.insize, 0); | |
| return data->cmd.insize; | |
| }); | |
| EXPECT_FALSE(mock_cmd.Run(kDummyFd)); | |
| } | |
| } // namespace | |
| } // namespace ec |