blob: 8c02eb800edf7eb548887da8b05b64e9b939f54a [file] [log] [blame]
// Copyright 2022 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 <lib/async-loop/cpp/loop.h>
#include <lib/fake-i2c/fake-i2c.h>
#include <zxtest/zxtest.h>
#include "args.h"
#include "i2cutil2.h"
namespace {
TEST(I2cArgsTest, TestBadCommand) {
const char* argv[] = {"i2cutil", "\0", "somepath"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_NOT_OK(parsed.status_value());
}
TEST(I2cArgsTest, TestPing) {
const char* argv[] = {"i2cutil", "ping"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_OK(parsed.status_value());
EXPECT_EQ(parsed->Op(), i2cutil::I2cOp::Ping);
EXPECT_EQ(parsed->Transactions().size(), 0);
EXPECT_EQ(parsed->Path(), "");
}
TEST(I2cArgsTest, TestPingIgnoreTrailing) {
const char* argv[] = {"i2cutil", "ping", "000", "0x01"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_OK(parsed.status_value());
EXPECT_EQ(parsed->Op(), i2cutil::I2cOp::Ping);
EXPECT_EQ(parsed->Transactions().size(), 0);
EXPECT_EQ(parsed->Path(), "");
}
TEST(I2cArgsTest, TestHelp) {
const char* argv[] = {"i2cutil", "help"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_OK(parsed.status_value());
EXPECT_EQ(parsed->Op(), i2cutil::I2cOp::Help);
EXPECT_EQ(parsed->Transactions().size(), 0);
EXPECT_EQ(parsed->Path(), "");
}
TEST(I2cArgsTest, TestReadPartialPath) {
const char* argv[] = {"i2cutil", "read", "000", "0x01"};
int argc = std::size(argv);
char buffer[32];
snprintf(buffer, std::size(buffer), i2cutil::kI2cPathFormat, 0);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_OK(parsed.status_value());
EXPECT_EQ(parsed->Op(), i2cutil::I2cOp::Read);
EXPECT_EQ(parsed->Path(), buffer);
}
TEST(I2cArgsTest, TestWriteFullPath) {
const char path[] = "/dev/class/i2c/000";
const char* argv[] = {"i2cutil", "write", path, "0x01"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_OK(parsed.status_value());
EXPECT_EQ(parsed->Op(), i2cutil::I2cOp::Write);
EXPECT_EQ(parsed->Path(), path);
}
TEST(I2cArgsTest, TestRFullPath) {
/// Make sure that 'r' works as a subcommand just as well as 'read'
const char path[] = "foobar";
const char* argv[] = {"i2cutil", "r", path, "0x01"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_OK(parsed.status_value());
EXPECT_EQ(parsed->Op(), i2cutil::I2cOp::Read);
EXPECT_EQ(parsed->Path(), path);
}
TEST(I2cArgsTest, TestInsufficientArgs) {
const char* argv[] = {"i2cutil"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_NOT_OK(parsed.status_value());
}
TEST(I2cArgsTest, TestReadSegmentData) {
const char path[] = "foobar";
const char* argv[] = {"i2cutil", "read", path, "0xab"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_OK(parsed.status_value());
EXPECT_EQ(parsed->Transactions().size(), 2);
// Write the address.
EXPECT_EQ(parsed->Transactions().at(0).type, i2cutil::TransactionType::Write);
ASSERT_EQ(parsed->Transactions().at(0).bytes.size(), 1);
EXPECT_EQ(parsed->Transactions().at(0).count, 0);
EXPECT_EQ(parsed->Transactions().at(0).bytes.at(0), 0xab);
// Read the result back.
EXPECT_EQ(parsed->Transactions().at(1).type, i2cutil::TransactionType::Read);
EXPECT_EQ(parsed->Transactions().at(1).bytes.size(), 0);
EXPECT_EQ(parsed->Transactions().at(1).count, 1);
}
TEST(I2cArgsTest, TestWriteSegmentData) {
const char path[] = "foobar";
const char* argv[] = {"i2cutil", "write", path, "0xab"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_OK(parsed.status_value());
EXPECT_EQ(parsed->Transactions().size(), 1);
EXPECT_EQ(parsed->Transactions().at(0).type, i2cutil::TransactionType::Write);
EXPECT_EQ(parsed->Transactions().at(0).count, 0);
ASSERT_EQ(parsed->Transactions().at(0).bytes.size(), 1);
EXPECT_EQ(parsed->Transactions().at(0).bytes.at(0), 0xab);
}
TEST(I2cArgsTest, TestSegmentDataOoB) {
const char path[] = "foobar";
const char* argv[] = {"i2cutil", "r", path, "0x100"}; // Byte must be between 0x00 and 0xff
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_NOT_OK(parsed.status_value());
}
TEST(I2cArgsTest, TestSegmentDataMultipleBytes) {
const char path[] = "foobar";
const char* argv[] = {"i2cutil", "write", path, "0x01", "0x02", "0x03"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_OK(parsed.status_value());
// Expect one transaction with 3 bytes.
EXPECT_EQ(parsed->Transactions().size(), 1);
EXPECT_EQ(parsed->Transactions().at(0).type, i2cutil::TransactionType::Write);
ASSERT_EQ(parsed->Transactions().at(0).bytes.size(), 3);
EXPECT_EQ(parsed->Transactions().at(0).bytes.at(0), 0x01);
EXPECT_EQ(parsed->Transactions().at(0).bytes.at(1), 0x02);
EXPECT_EQ(parsed->Transactions().at(0).bytes.at(2), 0x03);
EXPECT_EQ(parsed->Transactions().at(0).bytes.at(2), 0x03);
EXPECT_EQ(parsed->Transactions().at(0).count, 0);
}
TEST(I2cArgsTest, TestParseTransaction) {
const char* argv[] = {"i2cutil", "transact", "somepath", "w", "0x20", "r", "3"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_OK(parsed.status_value());
ASSERT_EQ(parsed->Transactions().size(), 2);
EXPECT_EQ(parsed->Transactions().at(0).type, i2cutil::TransactionType::Write);
ASSERT_EQ(parsed->Transactions().at(0).bytes.size(), 1);
EXPECT_EQ(parsed->Transactions().at(0).bytes.at(0), 0x20);
EXPECT_EQ(parsed->Transactions().at(1).type, i2cutil::TransactionType::Read);
EXPECT_EQ(parsed->Transactions().at(1).bytes.size(), 0);
EXPECT_EQ(parsed->Transactions().at(1).count, 3);
}
TEST(I2cArgsTest, TestParseTransactionNoOpcode) {
const char* argv[] = {"i2cutil", "transact", "somepath",
"0x20", "r", "3"}; // Transaction must start with 'w' or 'r'
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_NOT_OK(parsed.status_value());
}
TEST(I2cArgsTest, TestParseTransactionMultibyte) {
const char* argv[] = {"i2cutil", "transact", "somepath", "w",
"0x01", "0x02", "0x03", // Transaction 1
"r", "5", // Transaction 2
"w", "0x07", "0x08", "0x09"}; // Transaction 3
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_OK(parsed.status_value());
// Expect 3 transactions with 3 bytes each.
EXPECT_EQ(parsed->Transactions().size(), 3);
// Transaction 1
EXPECT_EQ(parsed->Transactions().at(0).type, i2cutil::TransactionType::Write);
ASSERT_EQ(parsed->Transactions().at(0).bytes.size(), 3);
EXPECT_EQ(parsed->Transactions().at(0).bytes.at(0), 0x01);
EXPECT_EQ(parsed->Transactions().at(0).bytes.at(1), 0x02);
EXPECT_EQ(parsed->Transactions().at(0).bytes.at(2), 0x03);
// Transaction 2
EXPECT_EQ(parsed->Transactions().at(1).type, i2cutil::TransactionType::Read);
ASSERT_EQ(parsed->Transactions().at(1).bytes.size(), 0);
ASSERT_EQ(parsed->Transactions().at(1).count, 5);
// Transaction 3
EXPECT_EQ(parsed->Transactions().at(2).type, i2cutil::TransactionType::Write);
ASSERT_EQ(parsed->Transactions().at(2).bytes.size(), 3);
EXPECT_EQ(parsed->Transactions().at(2).bytes.at(0), 0x07);
EXPECT_EQ(parsed->Transactions().at(2).bytes.at(1), 0x08);
EXPECT_EQ(parsed->Transactions().at(2).bytes.at(2), 0x09);
}
TEST(I2cArgsTest, TestParseTransactionBadRead) {
const char* argv[] = {"i2cutil", "transact", "somepath",
"r", "1", "2"}; // 'r' must be followed by exactly 1 number
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_NOT_OK(parsed.status_value());
}
TEST(I2cArgsTest, TestCreateDumpTransactions) {
const char* argv[] = {"i2cutil", "dump", "somepath", "0x10", "0x3"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
ASSERT_OK(parsed.status_value());
// We asked for count = 0x3 registers which means that args should generate
// 3 reg * 2 txn/reg = 6txn.
ASSERT_EQ(parsed->Transactions().size(), 6);
for (size_t i = 0; i < parsed->Transactions().size(); i++) {
if (i % 2 == 0) {
EXPECT_EQ(parsed->Transactions()[i].type, i2cutil::TransactionType::Write);
} else {
EXPECT_EQ(parsed->Transactions()[i].type, i2cutil::TransactionType::Read);
}
}
}
TEST(I2cArgsTest, TestCreateDumpTransactionsOutOfRange) {
{
const char* argv[] = {"i2cutil", "dump", "somepath", "256", "10"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
EXPECT_NOT_OK(parsed.status_value());
}
{
const char* argv[] = {"i2cutil", "dump", "somepath", "0", "256"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
EXPECT_NOT_OK(parsed.status_value());
}
{
const char* argv[] = {"i2cutil", "dump", "somepath", "129", "129"};
int argc = std::size(argv);
auto parsed = i2cutil::Args::FromArgv(argc, argv);
EXPECT_NOT_OK(parsed.status_value());
}
}
class FakeI2cDevice : public fake_i2c::FakeI2c {
public:
void PushReadByte(uint8_t byte) { reads_.push_back(byte); }
const std::vector<uint8_t>& Reads() { return reads_; }
const std::vector<uint8_t>& Writes() { return writes_; }
protected:
zx_status_t Transact(const uint8_t* write_buffer, size_t write_buffer_size, uint8_t* read_buffer,
size_t* read_buffer_size) override {
writes_.insert(writes_.end(), write_buffer, write_buffer + write_buffer_size);
assert(reads_.size() <= fuchsia_hardware_i2c::wire::kMaxTransferSize);
memcpy(read_buffer, reads_.data(), reads_.size());
*read_buffer_size = reads_.size();
return ZX_OK;
}
std::vector<uint8_t> reads_;
std::vector<uint8_t> writes_;
};
class I2cUtilTest : public zxtest::Test {
public:
void SetUp() override {
loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigNeverAttachToThread);
auto server = fidl::CreateEndpoints(&client_);
ASSERT_OK(server.status_value());
fidl::BindServer(loop_->dispatcher(), std::move(server.value()),
static_cast<fidl::WireServer<fuchsia_hardware_i2c::Device>*>(&i2c_));
ASSERT_OK(loop_->StartThread("i2cutil-test-loop"));
}
void TearDown() override { loop_->Shutdown(); }
std::vector<i2cutil::TransactionData> CreateTransactions(size_t n);
protected:
std::unique_ptr<async::Loop> loop_;
fidl::ClientEnd<fuchsia_hardware_i2c::Device> client_;
FakeI2cDevice i2c_;
};
std::vector<i2cutil::TransactionData> I2cUtilTest::CreateTransactions(size_t n) {
std::vector<i2cutil::TransactionData> transactions;
transactions.reserve(n);
for (size_t i = 0; i < n; i++) {
i2cutil::TransactionData t;
t.type = i2cutil::TransactionType::Write;
t.bytes.push_back(static_cast<uint8_t>(i));
transactions.push_back(t);
}
return transactions;
}
TEST_F(I2cUtilTest, TestWriteBytes) {
i2cutil::TransactionData t1;
t1.type = i2cutil::TransactionType::Write;
t1.bytes.push_back(0x1);
t1.bytes.push_back(0x2);
t1.bytes.push_back(0x3);
std::vector<i2cutil::TransactionData> transactions;
transactions.push_back(t1);
i2cutil::execute(std::move(client_), transactions);
EXPECT_EQ(i2c_.Reads().size(), 0);
ASSERT_EQ(i2c_.Writes().size(), 3);
EXPECT_EQ(i2c_.Writes().at(0), 0x1);
EXPECT_EQ(i2c_.Writes().at(1), 0x2);
EXPECT_EQ(i2c_.Writes().at(2), 0x3);
}
TEST_F(I2cUtilTest, TestReadBytes) {
i2cutil::TransactionData t1;
t1.type = i2cutil::TransactionType::Read;
t1.count = 3;
std::vector<i2cutil::TransactionData> transactions;
transactions.push_back(t1);
i2c_.PushReadByte(0x01);
i2c_.PushReadByte(0x02);
i2c_.PushReadByte(0x03);
i2cutil::execute(std::move(client_), transactions);
EXPECT_EQ(i2c_.Writes().size(), 0);
EXPECT_EQ(transactions.size(), 1);
ASSERT_EQ(transactions.at(0).bytes.size(), 3);
EXPECT_EQ(transactions.at(0).bytes.at(0), 0x01);
EXPECT_EQ(transactions.at(0).bytes.at(1), 0x02);
EXPECT_EQ(transactions.at(0).bytes.at(2), 0x03);
}
TEST_F(I2cUtilTest, TestMultiTransaction) {
i2cutil::TransactionData t1;
t1.type = i2cutil::TransactionType::Write;
t1.bytes.push_back(0x1);
t1.bytes.push_back(0x2);
t1.bytes.push_back(0x3);
i2c_.PushReadByte(0x4);
i2c_.PushReadByte(0x5);
i2c_.PushReadByte(0x6);
i2cutil::TransactionData t2;
t2.type = i2cutil::TransactionType::Read;
t2.count = 3;
i2cutil::TransactionData t3;
t3.type = i2cutil::TransactionType::Write;
t3.bytes.push_back(0x7);
t3.bytes.push_back(0x8);
t3.bytes.push_back(0x9);
std::vector<i2cutil::TransactionData> transactions = {t1, t2, t3};
i2cutil::execute(std::move(client_), transactions);
ASSERT_EQ(i2c_.Writes().size(), 6);
EXPECT_EQ(i2c_.Writes().at(0), 0x1);
EXPECT_EQ(i2c_.Writes().at(1), 0x2);
EXPECT_EQ(i2c_.Writes().at(2), 0x3);
EXPECT_EQ(i2c_.Writes().at(3), 0x7);
EXPECT_EQ(i2c_.Writes().at(4), 0x8);
EXPECT_EQ(i2c_.Writes().at(5), 0x9);
ASSERT_EQ(transactions.size(), 3);
ASSERT_EQ(transactions.at(1).bytes.size(), 3);
EXPECT_EQ(transactions.at(1).bytes.at(0), 0x4);
EXPECT_EQ(transactions.at(1).bytes.at(1), 0x5);
EXPECT_EQ(transactions.at(1).bytes.at(2), 0x6);
}
TEST_F(I2cUtilTest, TestPaginatedLessThanOnePage) {
// In this test we want to test that pagination works correctly when there are
// fewer than one page worth of transactions.
constexpr size_t kNumTransactions = i2cutil::kMaxTransactionCount - 1;
std::vector<i2cutil::TransactionData> transactions = CreateTransactions(kNumTransactions);
i2cutil::execute(std::move(client_), transactions);
EXPECT_EQ(i2c_.Writes().size(), transactions.size());
}
TEST_F(I2cUtilTest, TestPaginatedExactlyOnePage) {
// In this test we want to test that pagination works correctly when the number
// of transactions exactly matches the max allowable number of transactions.
constexpr size_t kNumTransactions = i2cutil::kMaxTransactionCount;
std::vector<i2cutil::TransactionData> transactions = CreateTransactions(kNumTransactions);
i2cutil::execute(std::move(client_), transactions);
EXPECT_EQ(i2c_.Writes().size(), transactions.size());
}
TEST_F(I2cUtilTest, TestPaginatedMoreThanOnePage) {
// In this test we want to test that pagination works correctly when there are
// more than one page worth of transactions.
constexpr size_t kNumTransactions = i2cutil::kMaxTransactionCount + 1;
std::vector<i2cutil::TransactionData> transactions = CreateTransactions(kNumTransactions);
i2cutil::execute(std::move(client_), transactions);
EXPECT_EQ(i2c_.Writes().size(), transactions.size());
}
} // namespace