blob: 0f2549781ee3869592ab341d3b3057fa90bf7260 [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));
};
class MockAddEntropyCommand
: public MockEcCommandAsync<struct ec_params_rollback_add_entropy,
EmptyParam> {
public:
MockAddEntropyCommand()
: MockEcCommandAsync(EC_CMD_ADD_ENTROPY, ADD_ENTROPY_GET_RESULT) {}
};
// 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;
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; 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.Run(
kDummyFd, {.poll_for_result_num_attempts = 2,
.poll_interval = base::TimeDelta::FromMilliseconds(1)}));
EXPECT_EQ(mock.Result(), EC_RES_SUCCESS);
}
TEST(EcCommandAsync, Run_TimeoutFailure) {
MockAddEntropyCommand mock;
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;
})
// 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.Run(
kDummyFd, {.poll_for_result_num_attempts = 2,
.poll_interval = base::TimeDelta::FromMilliseconds(1)}));
EXPECT_EQ(mock.Result(), EC_RES_BUSY);
}
TEST(EcCommandAsync, Run_Failure) {
MockAddEntropyCommand mock;
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; EC returns error.
.WillOnce([](int, uint32_t, MockAddEntropyCommand::Data* data) {
data->cmd.result = EC_RES_ERROR;
return data->cmd.insize;
});
EXPECT_FALSE(mock.Run(
kDummyFd, {// 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_EQ(mock.Result(), EC_RES_ERROR);
}
TEST(EcCommandAsync, Run_IoctlTimesOut) {
MockAddEntropyCommand mock;
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, {
// 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_EQ(mock.Result(), 255);
}
TEST(EcCommandAsync, Run_IoctlTimesOut_IgnoreFailure) {
MockAddEntropyCommand mock;
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, {.poll_for_result_num_attempts = 2,
.poll_interval = base::TimeDelta::FromMilliseconds(1),
.validate_poll_result = false}));
EXPECT_EQ(mock.Result(), EC_RES_SUCCESS);
}
TEST(EcCommandAsync, Run_InvalidOptions_ZeroPollAttempts) {
MockAddEntropyCommand mock;
EXPECT_DEATH(mock.Run(kDummyFd, {.poll_for_result_num_attempts = 0}),
"options.poll_for_result_num_attempts > 0");
}
TEST(EcCommandAsync, Run_InvalidOptions_NegativePollAttempts) {
MockAddEntropyCommand mock;
EXPECT_DEATH(mock.Run(kDummyFd, {.poll_for_result_num_attempts = -1}),
"options.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