blob: 1d8c8bae021a07015a78d2e4d6c21b3f782fd6cc [file] [log] [blame]
// Copyright 2019 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.
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "biod/ec_command_async.h"
using testing::_;
using testing::InvokeWithoutArgs;
using testing::Return;
namespace biod {
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) {}
};
// 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)
TEST(EcCommandAsync, Run_Success) {
MockAddEntropyCommand mock_cmd(
{.poll_for_result_num_attempts = 2,
.poll_interval = base::TimeDelta::FromMilliseconds(1)});
EXPECT_CALL(mock_cmd, ioctl)
.Times(3)
// First call to ioctl() to start the command; EC returns success.
.WillOnce([](int, uint32_t, MockAddEntropyCommand::Data* data) {
data->cmd.result = EC_RES_SUCCESS;
return data->cmd.insize;
})
// Second call to ioctl() to get the result; EC returns busy.
.WillOnce([](int, uint32_t, MockAddEntropyCommand::Data* data) {
data->cmd.result = EC_RES_BUSY;
return data->cmd.insize;
})
// Third call to ioctl() to get the result; EC returns success.
.WillOnce([](int, uint32_t, MockAddEntropyCommand::Data* data) {
data->cmd.result = EC_RES_SUCCESS;
return data->cmd.insize;
});
EXPECT_TRUE(mock_cmd.Run(kDummyFd));
EXPECT_EQ(mock_cmd.Result(), EC_RES_SUCCESS);
}
TEST(EcCommandAsync, Run_TimeoutFailure) {
MockAddEntropyCommand mock_cmd(
{.poll_for_result_num_attempts = 2,
.poll_interval = base::TimeDelta::FromMilliseconds(1)});
EXPECT_CALL(mock_cmd, ioctl)
.Times(3)
// First call to ioctl() to start the command; EC returns success.
.WillOnce([](int, uint32_t, MockAddEntropyCommand::Data* data) {
data->cmd.result = EC_RES_SUCCESS;
return data->cmd.insize;
})
// All remaining ioctl() calls; EC returns busy.
.WillRepeatedly([](int, uint32_t, MockAddEntropyCommand::Data* data) {
data->cmd.result = EC_RES_BUSY;
return data->cmd.insize;
});
EXPECT_FALSE(mock_cmd.Run(kDummyFd));
EXPECT_EQ(mock_cmd.Result(), EC_RES_BUSY);
}
TEST(EcCommandAsync, Run_Failure) {
MockAddEntropyCommand 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::TimeDelta::FromMilliseconds(1)});
EXPECT_CALL(mock_cmd, ioctl)
.Times(2)
// First call to ioctl() to start the command; EC returns success.
.WillOnce([](int, uint32_t, MockAddEntropyCommand::Data* data) {
data->cmd.result = EC_RES_SUCCESS;
return data->cmd.insize;
})
// Second call to ioctl() to get the result; EC returns error.
.WillOnce([](int, uint32_t, MockAddEntropyCommand::Data* data) {
data->cmd.result = EC_RES_ERROR;
return data->cmd.insize;
});
EXPECT_FALSE(mock_cmd.Run(kDummyFd));
EXPECT_EQ(mock_cmd.Result(), EC_RES_ERROR);
}
TEST(EcCommandAsync, Run_IoctlTimesOut) {
MockAddEntropyCommand 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::TimeDelta::FromMilliseconds(1),
});
EXPECT_CALL(mock, ioctl)
.Times(2)
// First call to ioctl() to start the command; EC returns success.
.WillOnce([](int, uint32_t, MockAddEntropyCommand::Data* data) {
data->cmd.result = EC_RES_SUCCESS;
return data->cmd.insize;
})
// Second call to ioctl() to get the result returns error (EC not
// responding).
.WillOnce([](int, uint32_t, MockAddEntropyCommand::Data* data) {
errno = ETIMEDOUT;
return kIoctlFailureRetVal;
});
EXPECT_FALSE(mock.Run(kDummyFd));
EXPECT_EQ(mock.Result(), kEcCommandUninitializedResult);
}
TEST(EcCommandAsync, Run_IoctlTimesOut_IgnoreFailure) {
MockAddEntropyCommand mock(
{.poll_for_result_num_attempts = 2,
.poll_interval = base::TimeDelta::FromMilliseconds(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, MockAddEntropyCommand::Data* data) {
data->cmd.result = EC_RES_SUCCESS;
return data->cmd.insize;
})
// Second call to ioctl() to get the result returns error; EC not
// responding.
.WillOnce([](int, uint32_t, MockAddEntropyCommand::Data* data) {
errno = ETIMEDOUT;
return kIoctlFailureRetVal;
})
// Third call to ioctl() to get the result; EC returns success.
.WillOnce([](int, uint32_t, MockAddEntropyCommand::Data* data) {
data->cmd.result = EC_RES_SUCCESS;
return data->cmd.insize;
});
EXPECT_TRUE(mock.Run(kDummyFd));
EXPECT_EQ(mock.Result(), EC_RES_SUCCESS);
}
TEST(EcCommandAsync, Run_InvalidOptions_ZeroPollAttempts) {
MockAddEntropyCommand mock({.poll_for_result_num_attempts = 0});
EXPECT_DEATH(mock.Run(kDummyFd), "poll_for_result_num_attempts > 0");
}
TEST(EcCommandAsync, Run_InvalidOptions_NegativePollAttempts) {
MockAddEntropyCommand mock({.poll_for_result_num_attempts = -1});
EXPECT_DEATH(mock.Run(kDummyFd), "poll_for_result_num_attempts > 0");
}
TEST(EcCommandAsync, DefaultOptions) {
MockAddEntropyCommand::Options options;
EXPECT_EQ(options.validate_poll_result, true);
EXPECT_EQ(options.poll_for_result_num_attempts, 20);
EXPECT_EQ(options.poll_interval, base::TimeDelta::FromMilliseconds(100));
}
} // namespace
} // namespace biod