blob: f1b8878191eede140068548803be2d746882764c [file] [log] [blame]
// 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "libec/ec_command_async.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