diff options
Diffstat (limited to 'test')
28 files changed, 2362 insertions, 0 deletions
diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000..29586bf --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,110 @@ +AM_CPPFLAGS = -I$(top_srcdir)/ \ + $(GTEST_CFLAGS) \ + $(GMOCK_CFLAGS) +AM_CXXFLAGS = \ + $(GTEST_MAIN_CFLAGS) +AM_LDFLAGS = \ + $(GMOCK_LIBS) \ + $(GTEST_MAIN_LIBS) \ + $(OESDK_TESTCASE_FLAGS) + +# Run all 'check' test programs +check_PROGRAMS = \ + ipmi_unittest \ + ipmi_getcount_unittest \ + ipmi_enumerate_unittest \ + ipmi_open_unittest \ + ipmi_close_unittest \ + ipmi_delete_unittest \ + ipmi_stat_unittest \ + ipmi_sessionstat_unittest \ + ipmi_commit_unittest \ + ipmi_read_unittest \ + ipmi_write_unittest \ + ipmi_validate_unittest \ + manager_unittest \ + manager_getsession_unittest \ + manager_open_unittest \ + manager_stat_unittest \ + manager_sessionstat_unittest \ + manager_commit_unittest \ + manager_close_unittest \ + manager_delete_unittest \ + manager_write_unittest \ + manager_read_unittest \ + process_unittest \ + crc_unittest +TESTS = $(check_PROGRAMS) + +ipmi_unittest_SOURCES = ipmi_unittest.cpp +ipmi_unittest_LDADD = $(top_builddir)/ipmi.o + +ipmi_getcount_unittest_SOURCES = ipmi_getcount_unittest.cpp +ipmi_getcount_unittest_LDADD = $(top_builddir)/ipmi.o + +ipmi_enumerate_unittest_SOURCES = ipmi_enumerate_unittest.cpp +ipmi_enumerate_unittest_LDADD = $(top_builddir)/ipmi.o + +ipmi_open_unittest_SOURCES = ipmi_open_unittest.cpp +ipmi_open_unittest_LDADD = $(top_builddir)/ipmi.o + +ipmi_close_unittest_SOURCES = ipmi_close_unittest.cpp +ipmi_close_unittest_LDADD = $(top_builddir)/ipmi.o + +ipmi_delete_unittest_SOURCES = ipmi_delete_unittest.cpp +ipmi_delete_unittest_LDADD = $(top_builddir)/ipmi.o + +ipmi_stat_unittest_SOURCES = ipmi_stat_unittest.cpp +ipmi_stat_unittest_LDADD = $(top_builddir)/ipmi.o + +ipmi_sessionstat_unittest_SOURCES = ipmi_sessionstat_unittest.cpp +ipmi_sessionstat_unittest_LDADD = $(top_builddir)/ipmi.o + +ipmi_commit_unittest_SOURCES = ipmi_commit_unittest.cpp +ipmi_commit_unittest_LDADD = $(top_builddir)/ipmi.o + +ipmi_read_unittest_SOURCES = ipmi_read_unittest.cpp +ipmi_read_unittest_LDADD = $(top_builddir)/ipmi.o + +ipmi_write_unittest_SOURCES = ipmi_write_unittest.cpp +ipmi_write_unittest_LDADD = $(top_builddir)/ipmi.o + +ipmi_validate_unittest_SOURCES = ipmi_validate_unittest.cpp +ipmi_validate_unittest_LDADD = $(top_builddir)/ipmi.o + +manager_unittest_SOURCES = manager_unittest.cpp +manager_unittest_LDADD = $(top_builddir)/manager.o + +manager_getsession_unittest_SOURCES = manager_getsession_unittest.cpp +manager_getsession_unittest_LDADD = $(top_builddir)/manager.o + +manager_open_unittest_SOURCES = manager_open_unittest.cpp +manager_open_unittest_LDADD = $(top_builddir)/manager.o + +manager_stat_unittest_SOURCES = manager_stat_unittest.cpp +manager_stat_unittest_LDADD = $(top_builddir)/manager.o + +manager_sessionstat_unittest_SOURCES = manager_sessionstat_unittest.cpp +manager_sessionstat_unittest_LDADD = $(top_builddir)/manager.o + +manager_commit_unittest_SOURCES = manager_commit_unittest.cpp +manager_commit_unittest_LDADD = $(top_builddir)/manager.o + +manager_close_unittest_SOURCES = manager_close_unittest.cpp +manager_close_unittest_LDADD = $(top_builddir)/manager.o + +manager_delete_unittest_SOURCES = manager_delete_unittest.cpp +manager_delete_unittest_LDADD = $(top_builddir)/manager.o + +manager_write_unittest_SOURCES = manager_write_unittest.cpp +manager_write_unittest_LDADD = $(top_builddir)/manager.o + +manager_read_unittest_SOURCES = manager_read_unittest.cpp +manager_read_unittest_LDADD = $(top_builddir)/manager.o + +process_unittest_SOURCES = process_unittest.cpp +process_unittest_LDADD = $(top_builddir)/process.o $(top_builddir)/ipmi.o \ + $(top_builddir)/crc.o + +crc_unittest_SOURCES = crc_unittest.cpp +crc_unittest_LDADD = $(top_builddir)/crc.o diff --git a/test/blob_mock.hpp b/test/blob_mock.hpp new file mode 100644 index 0000000..6c21c65 --- /dev/null +++ b/test/blob_mock.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "blobs.hpp" + +#include <gmock/gmock.h> + +namespace blobs +{ + +class BlobMock : public GenericBlobInterface +{ + public: + virtual ~BlobMock() = default; + + MOCK_METHOD1(canHandleBlob, bool(const std::string&)); + MOCK_METHOD0(getBlobIds, std::vector<std::string>()); + MOCK_METHOD1(deleteBlob, bool(const std::string&)); + MOCK_METHOD2(stat, bool(const std::string&, struct BlobMeta*)); + MOCK_METHOD3(open, bool(uint16_t, uint16_t, const std::string&)); + MOCK_METHOD3(read, std::vector<uint8_t>(uint16_t, uint32_t, uint32_t)); + MOCK_METHOD3(write, bool(uint16_t, uint32_t, const std::vector<uint8_t>&)); + MOCK_METHOD2(commit, bool(uint16_t, const std::vector<uint8_t>&)); + MOCK_METHOD1(close, bool(uint16_t)); + MOCK_METHOD2(stat, bool(uint16_t, struct BlobMeta*)); + MOCK_METHOD1(expire, bool(uint16_t)); +}; +} // namespace blobs diff --git a/test/crc_mock.hpp b/test/crc_mock.hpp new file mode 100644 index 0000000..1562200 --- /dev/null +++ b/test/crc_mock.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "crc.hpp" + +#include <gmock/gmock.h> + +namespace blobs +{ + +class CrcMock : public CrcInterface +{ + public: + virtual ~CrcMock() = default; + + MOCK_METHOD0(clear, void()); + MOCK_METHOD2(compute, void(const uint8_t*, uint32_t)); + MOCK_CONST_METHOD0(get, uint16_t()); +}; +} // namespace blobs diff --git a/test/crc_unittest.cpp b/test/crc_unittest.cpp new file mode 100644 index 0000000..fb69cb4 --- /dev/null +++ b/test/crc_unittest.cpp @@ -0,0 +1,44 @@ +#include "crc.hpp" + +#include <string> +#include <vector> + +#include <gtest/gtest.h> + +namespace blobs +{ + +TEST(Crc16Test, VerifyCrcValue) +{ + // Verify the crc16 is producing the value we expect. + + // Origin: security/crypta/ipmi/portable/ipmi_utils_test.cc + struct CrcTestVector + { + std::string input; + uint16_t output; + }; + + std::string longString = + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAA"; + + std::vector<CrcTestVector> vectors({{"", 0x1D0F}, + {"A", 0x9479}, + {"123456789", 0xE5CC}, + {longString, 0xE938}}); + + Crc16 crc; + + for (const CrcTestVector& testVector : vectors) + { + crc.clear(); + auto data = reinterpret_cast<const uint8_t*>(testVector.input.data()); + crc.compute(data, testVector.input.size()); + EXPECT_EQ(crc.get(), testVector.output); + } +} +} // namespace blobs diff --git a/test/ipmi_close_unittest.cpp b/test/ipmi_close_unittest.cpp new file mode 100644 index 0000000..e34f731 --- /dev/null +++ b/test/ipmi_close_unittest.cpp @@ -0,0 +1,66 @@ +#include "ipmi.hpp" +#include "manager_mock.hpp" + +#include <cstring> +#include <string> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::Invoke; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::StrEq; + +// ipmid.hpp isn't installed where we can grab it and this value is per BMC +// SoC. +#define MAX_IPMI_BUFFER 64 + +TEST(BlobCloseTest, ManagerRejectsCloseReturnsFailure) +{ + // The session manager returned failure to close, which we need to pass on. + + ManagerMock mgr; + uint16_t sessionId = 0x54; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + struct BmcBlobCloseTx req; + + req.cmd = BlobOEMCommands::bmcBlobClose; + req.crc = 0; + req.sessionId = sessionId; + + dataLen = sizeof(req); + + std::memcpy(request, &req, sizeof(req)); + + EXPECT_CALL(mgr, close(sessionId)).WillOnce(Return(false)); + EXPECT_EQ(IPMI_CC_INVALID, closeBlob(&mgr, request, reply, &dataLen)); +} + +TEST(BlobCloseTest, BlobClosedReturnsSuccess) +{ + // Verify that if all goes right, success is returned. + + ManagerMock mgr; + uint16_t sessionId = 0x54; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + struct BmcBlobCloseTx req; + + req.cmd = BlobOEMCommands::bmcBlobClose; + req.crc = 0; + req.sessionId = sessionId; + + dataLen = sizeof(req); + + std::memcpy(request, &req, sizeof(req)); + + EXPECT_CALL(mgr, close(sessionId)).WillOnce(Return(true)); + EXPECT_EQ(IPMI_CC_OK, closeBlob(&mgr, request, reply, &dataLen)); +} +} // namespace blobs diff --git a/test/ipmi_commit_unittest.cpp b/test/ipmi_commit_unittest.cpp new file mode 100644 index 0000000..1cc47a4 --- /dev/null +++ b/test/ipmi_commit_unittest.cpp @@ -0,0 +1,112 @@ +#include "ipmi.hpp" +#include "manager_mock.hpp" + +#include <cstring> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::_; +using ::testing::ElementsAreArray; +using ::testing::Return; + +// ipmid.hpp isn't installed where we can grab it and this value is per BMC +// SoC. +#define MAX_IPMI_BUFFER 64 + +TEST(BlobCommitTest, InvalidCommitDataLengthReturnsFailure) +{ + // The commit command supports an optional commit blob. This test verifies + // we sanity check the length of that blob. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobCommitTx*>(request); + + req->cmd = BlobOEMCommands::bmcBlobCommit; + req->crc = 0; + req->sessionId = 0x54; + req->commitDataLen = + 1; // It's one byte, but that's more than the packet size. + + dataLen = sizeof(struct BmcBlobCommitTx); + + EXPECT_EQ(IPMI_CC_INVALID, commitBlob(&mgr, request, reply, &dataLen)); +} + +TEST(BlobCommitTest, ValidCommitNoDataHandlerRejectsReturnsFailure) +{ + // The commit packet is valid and the manager's commit call returns failure. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobCommitTx*>(request); + + req->cmd = BlobOEMCommands::bmcBlobCommit; + req->crc = 0; + req->sessionId = 0x54; + req->commitDataLen = 0; + + dataLen = sizeof(struct BmcBlobCommitTx); + + EXPECT_CALL(mgr, commit(req->sessionId, _)).WillOnce(Return(false)); + + EXPECT_EQ(IPMI_CC_INVALID, commitBlob(&mgr, request, reply, &dataLen)); +} + +TEST(BlobCommitTest, ValidCommitNoDataHandlerAcceptsReturnsSuccess) +{ + // Commit called with no data and everything returns success. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobCommitTx*>(request); + + req->cmd = BlobOEMCommands::bmcBlobCommit; + req->crc = 0; + req->sessionId = 0x54; + req->commitDataLen = 0; + + dataLen = sizeof(struct BmcBlobCommitTx); + + EXPECT_CALL(mgr, commit(req->sessionId, _)).WillOnce(Return(true)); + + EXPECT_EQ(IPMI_CC_OK, commitBlob(&mgr, request, reply, &dataLen)); +} + +TEST(BlobCommitTest, ValidCommitWithDataHandlerAcceptsReturnsSuccess) +{ + // Commit called with extra data and everything returns success. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobCommitTx*>(request); + + uint8_t expectedBlob[4] = {0x25, 0x33, 0x45, 0x67}; + + req->cmd = BlobOEMCommands::bmcBlobCommit; + req->crc = 0; + req->sessionId = 0x54; + req->commitDataLen = sizeof(expectedBlob); + std::memcpy(req->commitData, &expectedBlob[0], sizeof(expectedBlob)); + + dataLen = sizeof(struct BmcBlobCommitTx) + sizeof(expectedBlob); + + EXPECT_CALL(mgr, + commit(req->sessionId, + ElementsAreArray(expectedBlob, sizeof(expectedBlob)))) + .WillOnce(Return(true)); + + EXPECT_EQ(IPMI_CC_OK, commitBlob(&mgr, request, reply, &dataLen)); +} +} // namespace blobs diff --git a/test/ipmi_delete_unittest.cpp b/test/ipmi_delete_unittest.cpp new file mode 100644 index 0000000..25fb06b --- /dev/null +++ b/test/ipmi_delete_unittest.cpp @@ -0,0 +1,89 @@ +#include "ipmi.hpp" +#include "manager_mock.hpp" + +#include <cstring> +#include <string> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::_; +using ::testing::Return; +using ::testing::StrEq; + +// ipmid.hpp isn't installed where we can grab it and this value is per BMC +// SoC. +#define MAX_IPMI_BUFFER 64 + +TEST(BlobDeleteTest, InvalidRequestLengthReturnsFailure) +{ + // There is a minimum blobId length of one character, this test verifies + // we check that. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobDeleteTx*>(request); + std::string blobId = "abc"; + + req->cmd = BlobOEMCommands::bmcBlobDelete; + req->crc = 0; + // length() doesn't include the nul-terminator. + std::memcpy(req->blobId, blobId.c_str(), blobId.length()); + + dataLen = sizeof(struct BmcBlobDeleteTx) + blobId.length(); + + EXPECT_EQ(IPMI_CC_INVALID, deleteBlob(&mgr, request, reply, &dataLen)); +} + +TEST(BlobDeleteTest, RequestRejectedReturnsFailure) +{ + // The blobId is rejected for any reason. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobDeleteTx*>(request); + std::string blobId = "a"; + + req->cmd = BlobOEMCommands::bmcBlobDelete; + req->crc = 0; + // length() doesn't include the nul-terminator, request buff is initialized + // to 0s + std::memcpy(req->blobId, blobId.c_str(), blobId.length()); + + dataLen = sizeof(struct BmcBlobDeleteTx) + blobId.length() + 1; + + EXPECT_CALL(mgr, deleteBlob(StrEq(blobId))).WillOnce(Return(false)); + + EXPECT_EQ(IPMI_CC_INVALID, deleteBlob(&mgr, request, reply, &dataLen)); +} + +TEST(BlobDeleteTest, BlobDeleteReturnsOk) +{ + // The boring case where the blobId is deleted. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobDeleteTx*>(request); + std::string blobId = "a"; + + req->cmd = BlobOEMCommands::bmcBlobDelete; + req->crc = 0; + // length() doesn't include the nul-terminator, request buff is initialized + // to 0s + std::memcpy(req->blobId, blobId.c_str(), blobId.length()); + + dataLen = sizeof(struct BmcBlobDeleteTx) + blobId.length() + 1; + + EXPECT_CALL(mgr, deleteBlob(StrEq(blobId))).WillOnce(Return(true)); + + EXPECT_EQ(IPMI_CC_OK, deleteBlob(&mgr, request, reply, &dataLen)); +} +} // namespace blobs diff --git a/test/ipmi_enumerate_unittest.cpp b/test/ipmi_enumerate_unittest.cpp new file mode 100644 index 0000000..232fe7a --- /dev/null +++ b/test/ipmi_enumerate_unittest.cpp @@ -0,0 +1,65 @@ +#include "ipmi.hpp" +#include "manager_mock.hpp" + +#include <cstring> +#include <string> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::Return; + +// ipmid.hpp isn't installed where we can grab it and this value is per BMC +// SoC. +#define MAX_IPMI_BUFFER 64 + +TEST(BlobEnumerateTest, VerifyIfRequestByIdInvalidReturnsFailure) +{ + // This tests to verify that if the index is invalid, it'll return failure. + + ManagerMock mgr; + size_t dataLen; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + struct BmcBlobEnumerateTx req; + uint8_t* request = reinterpret_cast<uint8_t*>(&req); + + req.cmd = BlobOEMCommands::bmcBlobEnumerate; + req.blobIdx = 0; + dataLen = sizeof(struct BmcBlobEnumerateTx); + + EXPECT_CALL(mgr, getBlobId(req.blobIdx)).WillOnce(Return("")); + + EXPECT_EQ(IPMI_CC_INVALID, enumerateBlob(&mgr, request, reply, &dataLen)); +} + +TEST(BlobEnumerateTest, BoringRequestByIdAndReceive) +{ + // This tests that if an index into the blob_id cache is valid, the command + // will return the blobId. + + ManagerMock mgr; + size_t dataLen; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + struct BmcBlobEnumerateTx req; + struct BmcBlobEnumerateRx* rep; + uint8_t* request = reinterpret_cast<uint8_t*>(&req); + std::string blobId = "/asdf"; + + req.cmd = BlobOEMCommands::bmcBlobEnumerate; + req.blobIdx = 0; + dataLen = sizeof(struct BmcBlobEnumerateTx); + + EXPECT_CALL(mgr, getBlobId(req.blobIdx)).WillOnce(Return(blobId)); + + EXPECT_EQ(IPMI_CC_OK, enumerateBlob(&mgr, request, reply, &dataLen)); + + // We're expecting this as a response. + // blobId.length + 1 + sizeof(uint16_t); + EXPECT_EQ(blobId.length() + 1 + sizeof(uint16_t), dataLen); + + rep = reinterpret_cast<struct BmcBlobEnumerateRx*>(reply); + EXPECT_EQ(0, std::memcmp(rep->blobId, blobId.c_str(), blobId.length() + 1)); +} +} // namespace blobs diff --git a/test/ipmi_getcount_unittest.cpp b/test/ipmi_getcount_unittest.cpp new file mode 100644 index 0000000..c6d74e6 --- /dev/null +++ b/test/ipmi_getcount_unittest.cpp @@ -0,0 +1,72 @@ +#include "ipmi.hpp" +#include "manager_mock.hpp" + +#include <cstring> +#include <vector> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::Return; + +// ipmid.hpp isn't installed where we can grab it and this value is per BMC +// SoC. +#define MAX_IPMI_BUFFER 64 + +// the request here is only the subcommand byte and therefore there's no invalid +// length check, etc to handle within the method. + +TEST(BlobCountTest, ReturnsZeroBlobs) +{ + // Calling BmcBlobGetCount if there are no handlers registered should just + // return that there are 0 blobs. + + ManagerMock mgr; + size_t dataLen; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + struct BmcBlobCountTx req; + struct BmcBlobCountRx rep; + uint8_t* request = reinterpret_cast<uint8_t*>(&req); + + req.cmd = BlobOEMCommands::bmcBlobGetCount; + dataLen = sizeof(req); + + rep.crc = 0; + rep.blobCount = 0; + + EXPECT_CALL(mgr, buildBlobList()).WillOnce(Return(0)); + + EXPECT_EQ(IPMI_CC_OK, getBlobCount(&mgr, request, reply, &dataLen)); + + EXPECT_EQ(sizeof(rep), dataLen); + EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep))); +} + +TEST(BlobCountTest, ReturnsTwoBlobs) +{ + // Calling BmcBlobGetCount with one handler registered that knows of two + // blobs will return that it found two blobs. + + ManagerMock mgr; + size_t dataLen; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + struct BmcBlobCountTx req; + struct BmcBlobCountRx rep; + uint8_t* request = reinterpret_cast<uint8_t*>(&req); + + req.cmd = BlobOEMCommands::bmcBlobGetCount; + dataLen = sizeof(req); + + rep.crc = 0; + rep.blobCount = 2; + + EXPECT_CALL(mgr, buildBlobList()).WillOnce(Return(2)); + + EXPECT_EQ(IPMI_CC_OK, getBlobCount(&mgr, request, reply, &dataLen)); + + EXPECT_EQ(sizeof(rep), dataLen); + EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep))); +} +} // namespace blobs diff --git a/test/ipmi_open_unittest.cpp b/test/ipmi_open_unittest.cpp new file mode 100644 index 0000000..db2a34f --- /dev/null +++ b/test/ipmi_open_unittest.cpp @@ -0,0 +1,108 @@ +#include "ipmi.hpp" +#include "manager_mock.hpp" + +#include <cstring> +#include <string> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::_; +using ::testing::Invoke; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::StrEq; + +// ipmid.hpp isn't installed where we can grab it and this value is per BMC +// SoC. +#define MAX_IPMI_BUFFER 64 + +TEST(BlobOpenTest, InvalidRequestLengthReturnsFailure) +{ + // There is a minimum blobId length of one character, this test verifies + // we check that. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobOpenTx*>(request); + std::string blobId = "abc"; + + req->cmd = BlobOEMCommands::bmcBlobOpen; + req->crc = 0; + req->flags = 0; + // length() doesn't include the nul-terminator. + std::memcpy(req->blobId, blobId.c_str(), blobId.length()); + + dataLen = sizeof(struct BmcBlobOpenTx) + blobId.length(); + + EXPECT_EQ(IPMI_CC_INVALID, openBlob(&mgr, request, reply, &dataLen)); +} + +TEST(BlobOpenTest, RequestRejectedReturnsFailure) +{ + // The blobId is rejected for any reason. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobOpenTx*>(request); + std::string blobId = "a"; + + req->cmd = BlobOEMCommands::bmcBlobOpen; + req->crc = 0; + req->flags = 0; + // length() doesn't include the nul-terminator, request buff is initialized + // to 0s + std::memcpy(req->blobId, blobId.c_str(), blobId.length()); + + dataLen = sizeof(struct BmcBlobOpenTx) + blobId.length() + 1; + + EXPECT_CALL(mgr, open(req->flags, StrEq(blobId), _)) + .WillOnce(Return(false)); + + EXPECT_EQ(IPMI_CC_INVALID, openBlob(&mgr, request, reply, &dataLen)); +} + +TEST(BlobOpenTest, BlobOpenReturnsOk) +{ + // The boring case where the blobId opens. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobOpenTx*>(request); + struct BmcBlobOpenRx rep; + std::string blobId = "a"; + + req->cmd = BlobOEMCommands::bmcBlobOpen; + req->crc = 0; + req->flags = 0; + // length() doesn't include the nul-terminator, request buff is initialized + // to 0s + std::memcpy(req->blobId, blobId.c_str(), blobId.length()); + + dataLen = sizeof(struct BmcBlobOpenTx) + blobId.length() + 1; + uint16_t returnedSession = 0x54; + + EXPECT_CALL(mgr, open(req->flags, StrEq(blobId), NotNull())) + .WillOnce(Invoke( + [&](uint16_t flags, const std::string& path, uint16_t* session) { + (*session) = returnedSession; + return true; + })); + + EXPECT_EQ(IPMI_CC_OK, openBlob(&mgr, request, reply, &dataLen)); + + rep.crc = 0; + rep.sessionId = returnedSession; + + EXPECT_EQ(sizeof(rep), dataLen); + EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep))); +} +} // namespace blobs diff --git a/test/ipmi_read_unittest.cpp b/test/ipmi_read_unittest.cpp new file mode 100644 index 0000000..b6dab55 --- /dev/null +++ b/test/ipmi_read_unittest.cpp @@ -0,0 +1,78 @@ +#include "ipmi.hpp" +#include "manager_mock.hpp" + +#include <cstring> +#include <vector> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::Return; + +// ipmid.hpp isn't installed where we can grab it and this value is per BMC +// SoC. +#define MAX_IPMI_BUFFER 64 + +TEST(BlobReadTest, ManagerReturnsNoData) +{ + // Verify that if no data is returned the IPMI command reply has no + // payload. The manager, in all failures, will just return 0 bytes. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobReadTx*>(request); + + req->cmd = BlobOEMCommands::bmcBlobRead; + req->crc = 0; + req->sessionId = 0x54; + req->offset = 0x100; + req->requestedSize = 0x10; + + dataLen = sizeof(struct BmcBlobReadTx); + + std::vector<uint8_t> data; + + EXPECT_CALL(mgr, read(req->sessionId, req->offset, req->requestedSize)) + .WillOnce(Return(data)); + + EXPECT_EQ(IPMI_CC_OK, readBlob(&mgr, request, reply, &dataLen)); + EXPECT_EQ(sizeof(struct BmcBlobReadRx), dataLen); +} + +TEST(BlobReadTest, ManagerReturnsData) +{ + // Verify that if data is returned, it's placed in the expected location. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobReadTx*>(request); + + req->cmd = BlobOEMCommands::bmcBlobRead; + req->crc = 0; + req->sessionId = 0x54; + req->offset = 0x100; + req->requestedSize = 0x10; + + dataLen = sizeof(struct BmcBlobReadTx); + + std::vector<uint8_t> data = {0x02, 0x03, 0x05, 0x06}; + + EXPECT_CALL(mgr, read(req->sessionId, req->offset, req->requestedSize)) + .WillOnce(Return(data)); + + EXPECT_EQ(IPMI_CC_OK, readBlob(&mgr, request, reply, &dataLen)); + EXPECT_EQ(sizeof(struct BmcBlobReadRx) + data.size(), dataLen); + EXPECT_EQ(0, std::memcmp(&reply[sizeof(struct BmcBlobReadRx)], data.data(), + data.size())); +} + +/* TODO(venture): We need a test that handles other checks such as if the size + * requested won't fit into a packet response. + */ +} // namespace blobs diff --git a/test/ipmi_sessionstat_unittest.cpp b/test/ipmi_sessionstat_unittest.cpp new file mode 100644 index 0000000..e1e1aad --- /dev/null +++ b/test/ipmi_sessionstat_unittest.cpp @@ -0,0 +1,121 @@ +#include "ipmi.hpp" +#include "manager_mock.hpp" + +#include <cstring> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Matcher; +using ::testing::NotNull; +using ::testing::Return; + +// ipmid.hpp isn't installed where we can grab it and this value is per BMC +// SoC. +#define MAX_IPMI_BUFFER 64 + +TEST(BlobSessionStatTest, RequestRejectedByManagerReturnsFailure) +{ + // If the session ID is invalid, the request must fail. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobSessionStatTx*>(request); + req->cmd = BlobOEMCommands::bmcBlobSessionStat; + req->crc = 0; + req->sessionId = 0x54; + + dataLen = sizeof(struct BmcBlobSessionStatTx); + + EXPECT_CALL(mgr, stat(Matcher<uint16_t>(req->sessionId), + Matcher<struct BlobMeta*>(_))) + .WillOnce(Return(false)); + + EXPECT_EQ(IPMI_CC_INVALID, sessionStatBlob(&mgr, request, reply, &dataLen)); +} + +TEST(BlobSessionStatTest, RequestSucceedsNoMetadata) +{ + // Stat request succeeeds but there were no metadata bytes. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobSessionStatTx*>(request); + req->cmd = BlobOEMCommands::bmcBlobSessionStat; + req->crc = 0; + req->sessionId = 0x54; + + dataLen = sizeof(struct BmcBlobSessionStatTx); + + struct BmcBlobStatRx rep; + rep.crc = 0x00; + rep.blobState = 0x01; + rep.size = 0x100; + rep.metadataLen = 0x00; + + EXPECT_CALL(mgr, stat(Matcher<uint16_t>(req->sessionId), + Matcher<struct BlobMeta*>(NotNull()))) + .WillOnce(Invoke([&](uint16_t session, struct BlobMeta* meta) { + meta->blobState = rep.blobState; + meta->size = rep.size; + return true; + })); + + EXPECT_EQ(IPMI_CC_OK, sessionStatBlob(&mgr, request, reply, &dataLen)); + + EXPECT_EQ(sizeof(rep), dataLen); + EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep))); +} + +TEST(BlobSessionStatTest, RequestSucceedsWithMetadata) +{ + // Stat request succeeds and there were metadata bytes. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobSessionStatTx*>(request); + req->cmd = BlobOEMCommands::bmcBlobSessionStat; + req->crc = 0; + req->sessionId = 0x54; + + dataLen = sizeof(struct BmcBlobSessionStatTx); + + struct BlobMeta lmeta; + lmeta.blobState = 0x01; + lmeta.size = 0x100; + lmeta.metadata.push_back(0x01); + lmeta.metadata.push_back(0x02); + lmeta.metadata.push_back(0x03); + lmeta.metadata.push_back(0x04); + + struct BmcBlobStatRx rep; + rep.crc = 0x00; + rep.blobState = lmeta.blobState; + rep.size = lmeta.size; + rep.metadataLen = lmeta.metadata.size(); + + EXPECT_CALL(mgr, stat(Matcher<uint16_t>(req->sessionId), + Matcher<struct BlobMeta*>(NotNull()))) + .WillOnce(Invoke([&](uint16_t session, struct BlobMeta* meta) { + (*meta) = lmeta; + return true; + })); + + EXPECT_EQ(IPMI_CC_OK, sessionStatBlob(&mgr, request, reply, &dataLen)); + + EXPECT_EQ(sizeof(rep) + lmeta.metadata.size(), dataLen); + EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep))); + EXPECT_EQ(0, std::memcmp(reply + sizeof(rep), lmeta.metadata.data(), + lmeta.metadata.size())); +} +} // namespace blobs diff --git a/test/ipmi_stat_unittest.cpp b/test/ipmi_stat_unittest.cpp new file mode 100644 index 0000000..a6f1dfe --- /dev/null +++ b/test/ipmi_stat_unittest.cpp @@ -0,0 +1,157 @@ +#include "ipmi.hpp" +#include "manager_mock.hpp" + +#include <cstring> +#include <string> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Matcher; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::StrEq; + +// ipmid.hpp isn't installed where we can grab it and this value is per BMC +// SoC. +#define MAX_IPMI_BUFFER 64 + +TEST(BlobStatTest, InvalidRequestLengthReturnsFailure) +{ + // There is a minimum blobId length of one character, this test verifies + // we check that. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobStatTx*>(request); + std::string blobId = "abc"; + + req->cmd = BlobOEMCommands::bmcBlobStat; + req->crc = 0; + // length() doesn't include the nul-terminator. + std::memcpy(req->blobId, blobId.c_str(), blobId.length()); + + dataLen = sizeof(struct BmcBlobStatTx) + blobId.length(); + + EXPECT_EQ(IPMI_CC_INVALID, statBlob(&mgr, request, reply, &dataLen)); +} + +TEST(BlobStatTest, RequestRejectedReturnsFailure) +{ + // The blobId is rejected for any reason. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobStatTx*>(request); + std::string blobId = "a"; + + req->cmd = BlobOEMCommands::bmcBlobStat; + req->crc = 0; + // length() doesn't include the nul-terminator, request buff is initialized + // to 0s + std::memcpy(req->blobId, blobId.c_str(), blobId.length()); + + dataLen = sizeof(struct BmcBlobStatTx) + blobId.length() + 1; + + EXPECT_CALL(mgr, stat(Matcher<const std::string&>(StrEq(blobId)), + Matcher<struct BlobMeta*>(_))) + .WillOnce(Return(false)); + + EXPECT_EQ(IPMI_CC_INVALID, statBlob(&mgr, request, reply, &dataLen)); +} + +TEST(BlobStatTest, RequestSucceedsNoMetadata) +{ + // Stat request succeeeds but there were no metadata bytes. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobStatTx*>(request); + std::string blobId = "a"; + + req->cmd = BlobOEMCommands::bmcBlobStat; + req->crc = 0; + // length() doesn't include the nul-terminator, request buff is initialized + // to 0s + std::memcpy(req->blobId, blobId.c_str(), blobId.length()); + + dataLen = sizeof(struct BmcBlobStatTx) + blobId.length() + 1; + + struct BmcBlobStatRx rep; + rep.crc = 0x00; + rep.blobState = 0x01; + rep.size = 0x100; + rep.metadataLen = 0x00; + + EXPECT_CALL(mgr, stat(Matcher<const std::string&>(StrEq(blobId)), + Matcher<struct BlobMeta*>(NotNull()))) + .WillOnce(Invoke([&](const std::string& path, struct BlobMeta* meta) { + meta->blobState = rep.blobState; + meta->size = rep.size; + return true; + })); + + EXPECT_EQ(IPMI_CC_OK, statBlob(&mgr, request, reply, &dataLen)); + + EXPECT_EQ(sizeof(rep), dataLen); + EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep))); +} + +TEST(BlobStatTest, RequestSucceedsWithMetadata) +{ + // Stat request succeeds and there were metadata bytes. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobStatTx*>(request); + std::string blobId = "a"; + + req->cmd = BlobOEMCommands::bmcBlobStat; + req->crc = 0; + // length() doesn't include the nul-terminator, request buff is initialized + // to 0s + std::memcpy(req->blobId, blobId.c_str(), blobId.length()); + + dataLen = sizeof(struct BmcBlobStatTx) + blobId.length() + 1; + + struct BlobMeta lmeta; + lmeta.blobState = 0x01; + lmeta.size = 0x100; + lmeta.metadata.push_back(0x01); + lmeta.metadata.push_back(0x02); + lmeta.metadata.push_back(0x03); + lmeta.metadata.push_back(0x04); + + struct BmcBlobStatRx rep; + rep.crc = 0x00; + rep.blobState = lmeta.blobState; + rep.size = lmeta.size; + rep.metadataLen = lmeta.metadata.size(); + + EXPECT_CALL(mgr, stat(Matcher<const std::string&>(StrEq(blobId)), + Matcher<struct BlobMeta*>(NotNull()))) + .WillOnce(Invoke([&](const std::string& path, struct BlobMeta* meta) { + (*meta) = lmeta; + return true; + })); + + EXPECT_EQ(IPMI_CC_OK, statBlob(&mgr, request, reply, &dataLen)); + + EXPECT_EQ(sizeof(rep) + lmeta.metadata.size(), dataLen); + EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep))); + EXPECT_EQ(0, std::memcmp(reply + sizeof(rep), lmeta.metadata.data(), + lmeta.metadata.size())); +} +} // namespace blobs diff --git a/test/ipmi_unittest.cpp b/test/ipmi_unittest.cpp new file mode 100644 index 0000000..8f27ed7 --- /dev/null +++ b/test/ipmi_unittest.cpp @@ -0,0 +1,60 @@ +#include "ipmi.hpp" + +#include <cstring> + +#include <gtest/gtest.h> + +namespace blobs +{ + +// ipmid.hpp isn't installed where we can grab it and this value is per BMC +// SoC. +#define MAX_IPMI_BUFFER 64 + +TEST(StringInputTest, NullPointerInput) +{ + // The method should verify it did receive a non-null input pointer. + + EXPECT_STREQ("", stringFromBuffer(NULL, 5).c_str()); +} + +TEST(StringInputTest, ZeroBytesInput) +{ + // Verify that if the input length is 0 that it'll return the empty string. + + const char* request = "asdf"; + EXPECT_STREQ("", stringFromBuffer(request, 0).c_str()); +} + +TEST(StringInputTest, NulTerminatorNotFound) +{ + // Verify that if there isn't a nul-terminator found in an otherwise valid + // string, it'll return the emptry string. + + char request[MAX_IPMI_BUFFER]; + std::memset(request, 'a', sizeof(request)); + EXPECT_STREQ("", stringFromBuffer(request, sizeof(request)).c_str()); +} + +TEST(StringInputTest, TwoNulsFound) +{ + // Verify it makes you use the entire data region for the string. + char request[MAX_IPMI_BUFFER]; + request[0] = 'a'; + request[1] = 0; + std::memset(&request[2], 'b', sizeof(request) - 2); + request[MAX_IPMI_BUFFER - 1] = 0; + + // This case has two strings, and the last character is a nul-terminator. + EXPECT_STREQ("", stringFromBuffer(request, sizeof(request)).c_str()); +} + +TEST(StringInputTest, NulTerminatorFound) +{ + // Verify that if it's provided a valid nul-terminated string, it'll + // return it. + + const char* request = "asdf"; + EXPECT_STREQ("asdf", stringFromBuffer(request, 5).c_str()); +} +} // namespace blobs diff --git a/test/ipmi_validate_unittest.cpp b/test/ipmi_validate_unittest.cpp new file mode 100644 index 0000000..6bf4200 --- /dev/null +++ b/test/ipmi_validate_unittest.cpp @@ -0,0 +1,44 @@ +#include "ipmi.hpp" +#include "manager_mock.hpp" + +#include <vector> + +#include <gtest/gtest.h> + +namespace blobs +{ + +TEST(IpmiValidateTest, VerifyCommandMinimumLengths) +{ + + struct TestCase + { + BlobOEMCommands cmd; + size_t len; + bool expect; + }; + + std::vector<TestCase> tests = { + {BlobOEMCommands::bmcBlobClose, sizeof(struct BmcBlobCloseTx) - 1, + false}, + {BlobOEMCommands::bmcBlobCommit, sizeof(struct BmcBlobCommitTx) - 1, + false}, + {BlobOEMCommands::bmcBlobDelete, sizeof(struct BmcBlobDeleteTx) + 1, + false}, + {BlobOEMCommands::bmcBlobEnumerate, + sizeof(struct BmcBlobEnumerateTx) - 1, false}, + {BlobOEMCommands::bmcBlobOpen, sizeof(struct BmcBlobOpenTx) + 1, false}, + {BlobOEMCommands::bmcBlobRead, sizeof(struct BmcBlobReadTx) - 1, false}, + {BlobOEMCommands::bmcBlobSessionStat, + sizeof(struct BmcBlobSessionStatTx) - 1, false}, + {BlobOEMCommands::bmcBlobStat, sizeof(struct BmcBlobStatTx) + 1, false}, + {BlobOEMCommands::bmcBlobWrite, sizeof(struct BmcBlobWriteTx), false}, + }; + + for (const auto& test : tests) + { + bool result = validateRequestLength(test.cmd, test.len); + EXPECT_EQ(result, test.expect); + } +} +} // namespace blobs diff --git a/test/ipmi_write_unittest.cpp b/test/ipmi_write_unittest.cpp new file mode 100644 index 0000000..55a1e3b --- /dev/null +++ b/test/ipmi_write_unittest.cpp @@ -0,0 +1,73 @@ +#include "ipmi.hpp" +#include "manager_mock.hpp" + +#include <cstring> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::ElementsAreArray; +using ::testing::Return; + +// ipmid.hpp isn't installed where we can grab it and this value is per BMC +// SoC. +#define MAX_IPMI_BUFFER 64 + +TEST(BlobWriteTest, ManagerReturnsFailureReturnsFailure) +{ + // This verifies a failure from the manager is passed back. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobWriteTx*>(request); + + req->cmd = BlobOEMCommands::bmcBlobWrite; + req->crc = 0; + req->sessionId = 0x54; + req->offset = 0x100; + + uint8_t expectedBytes[2] = {0x66, 0x67}; + std::memcpy(req->data, &expectedBytes[0], sizeof(expectedBytes)); + + dataLen = sizeof(struct BmcBlobWriteTx) + sizeof(expectedBytes); + + EXPECT_CALL(mgr, + write(req->sessionId, req->offset, + ElementsAreArray(expectedBytes, sizeof(expectedBytes)))) + .WillOnce(Return(false)); + + EXPECT_EQ(IPMI_CC_INVALID, writeBlob(&mgr, request, reply, &dataLen)); +} + +TEST(BlobWriteTest, ManagerReturnsTrueWriteSucceeds) +{ + // The case where everything works. + + ManagerMock mgr; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + auto req = reinterpret_cast<struct BmcBlobWriteTx*>(request); + + req->cmd = BlobOEMCommands::bmcBlobWrite; + req->crc = 0; + req->sessionId = 0x54; + req->offset = 0x100; + + uint8_t expectedBytes[2] = {0x66, 0x67}; + std::memcpy(req->data, &expectedBytes[0], sizeof(expectedBytes)); + + dataLen = sizeof(struct BmcBlobWriteTx) + sizeof(expectedBytes); + + EXPECT_CALL(mgr, + write(req->sessionId, req->offset, + ElementsAreArray(expectedBytes, sizeof(expectedBytes)))) + .WillOnce(Return(true)); + + EXPECT_EQ(IPMI_CC_OK, writeBlob(&mgr, request, reply, &dataLen)); +} +} // namespace blobs diff --git a/test/manager_close_unittest.cpp b/test/manager_close_unittest.cpp new file mode 100644 index 0000000..47c9264 --- /dev/null +++ b/test/manager_close_unittest.cpp @@ -0,0 +1,66 @@ +#include "blob_mock.hpp" +#include "manager.hpp" + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::_; +using ::testing::Return; + +TEST(ManagerCloseTest, CloseNoSessionReturnsFalse) +{ + // Calling Close on a session that doesn't exist should return false. + + BlobManager mgr; + uint16_t sess = 1; + + EXPECT_FALSE(mgr.close(sess)); +} + +TEST(ManagerCloseTest, CloseSessionFoundButHandlerReturnsFalse) +{ + // The handler was found but it returned failure. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = OpenFlags::read, sess; + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true)); + EXPECT_TRUE(mgr.open(flags, path, &sess)); + + EXPECT_CALL(*m1ptr, close(sess)).WillOnce(Return(false)); + + EXPECT_FALSE(mgr.close(sess)); + + // TODO(venture): The session wasn't closed, need to verify. Could call + // public GetHandler method. +} + +TEST(ManagerCloseTest, CloseSessionFoundAndHandlerReturnsSuccess) +{ + // The handler was found and returned success. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = OpenFlags::read, sess; + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true)); + EXPECT_TRUE(mgr.open(flags, path, &sess)); + + EXPECT_CALL(*m1ptr, close(sess)).WillOnce(Return(true)); + + EXPECT_TRUE(mgr.close(sess)); +} +} // namespace blobs diff --git a/test/manager_commit_unittest.cpp b/test/manager_commit_unittest.cpp new file mode 100644 index 0000000..b1b3c8c --- /dev/null +++ b/test/manager_commit_unittest.cpp @@ -0,0 +1,68 @@ +#include "blob_mock.hpp" +#include "manager.hpp" + +#include <vector> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::_; +using ::testing::Return; + +TEST(ManagerCommitTest, CommitNoSessionReturnsFalse) +{ + // Calling Commit on a session that doesn't exist should return false. + + BlobManager mgr; + uint16_t sess = 1; + std::vector<uint8_t> data; + + EXPECT_FALSE(mgr.commit(sess, data)); +} + +TEST(ManagerCommitTest, CommitSessionFoundButHandlerReturnsFalse) +{ + // The handler was found but it returned failure. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = OpenFlags::write, sess; + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true)); + EXPECT_TRUE(mgr.open(flags, path, &sess)); + + std::vector<uint8_t> data; + EXPECT_CALL(*m1ptr, commit(sess, data)).WillOnce(Return(false)); + + EXPECT_FALSE(mgr.commit(sess, data)); +} + +TEST(ManagerCommitTest, CommitSessionFoundAndHandlerReturnsSuccess) +{ + // The handler was found and returned success. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = OpenFlags::write, sess; + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true)); + EXPECT_TRUE(mgr.open(flags, path, &sess)); + + std::vector<uint8_t> data; + EXPECT_CALL(*m1ptr, commit(sess, data)).WillOnce(Return(true)); + + EXPECT_TRUE(mgr.commit(sess, data)); +} +} // namespace blobs diff --git a/test/manager_delete_unittest.cpp b/test/manager_delete_unittest.cpp new file mode 100644 index 0000000..9ad3afd --- /dev/null +++ b/test/manager_delete_unittest.cpp @@ -0,0 +1,87 @@ +#include "blob_mock.hpp" +#include "manager.hpp" + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::_; +using ::testing::Return; + +TEST(ManagerDeleteTest, FileIsOpenReturnsFailure) +{ + // The blob manager maintains a naive list of open files and will + // return failure if you try to delete an open file. + + // Open the file. + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = OpenFlags::read, sess; + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillRepeatedly(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true)); + EXPECT_TRUE(mgr.open(flags, path, &sess)); + + // Try to delete the file. + EXPECT_FALSE(mgr.deleteBlob(path)); +} + +TEST(ManagerDeleteTest, FileHasNoHandler) +{ + // The blob manager cannot find any handler. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(false)); + + // Try to delete the file. + EXPECT_FALSE(mgr.deleteBlob(path)); +} + +TEST(ManagerDeleteTest, FileIsNotOpenButHandlerDeleteFails) +{ + // The Blob manager finds the handler but the handler returns failure + // on delete. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, deleteBlob(path)).WillOnce(Return(false)); + + // Try to delete the file. + EXPECT_FALSE(mgr.deleteBlob(path)); +} + +TEST(ManagerDeleteTest, FileIsNotOpenAndHandlerSucceeds) +{ + // The Blob manager finds the handler and the handler returns success. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, deleteBlob(path)).WillOnce(Return(true)); + + // Try to delete the file. + EXPECT_TRUE(mgr.deleteBlob(path)); +} +} // namespace blobs diff --git a/test/manager_getsession_unittest.cpp b/test/manager_getsession_unittest.cpp new file mode 100644 index 0000000..e66729a --- /dev/null +++ b/test/manager_getsession_unittest.cpp @@ -0,0 +1,24 @@ +#include "manager.hpp" + +#include <gtest/gtest.h> + +namespace blobs +{ + +TEST(ManagerGetSessionTest, NextSessionReturned) +{ + // This test verifies the next session ID is returned. + BlobManager mgr; + + uint16_t first, second; + EXPECT_TRUE(mgr.getSession(&first)); + EXPECT_TRUE(mgr.getSession(&second)); + EXPECT_FALSE(first == second); +} + +TEST(ManagerGetSessionTest, SessionsCheckedAgainstList) +{ + // TODO(venture): Need a test that verifies the session ids are checked + // against open sessions. +} +} // namespace blobs diff --git a/test/manager_mock.hpp b/test/manager_mock.hpp new file mode 100644 index 0000000..41979ac --- /dev/null +++ b/test/manager_mock.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "blobs.hpp" +#include "manager.hpp" + +#include <memory> +#include <string> + +#include <gmock/gmock.h> + +namespace blobs +{ + +class ManagerMock : public ManagerInterface +{ + public: + virtual ~ManagerMock() = default; + + MOCK_METHOD1(registerHandler, bool(std::unique_ptr<GenericBlobInterface>)); + MOCK_METHOD0(buildBlobList, uint32_t()); + MOCK_METHOD1(getBlobId, std::string(uint32_t)); + MOCK_METHOD3(open, bool(uint16_t, const std::string&, uint16_t*)); + MOCK_METHOD2(stat, bool(const std::string&, struct BlobMeta*)); + MOCK_METHOD2(stat, bool(uint16_t, struct BlobMeta*)); + MOCK_METHOD2(commit, bool(uint16_t, const std::vector<uint8_t>&)); + MOCK_METHOD1(close, bool(uint16_t)); + MOCK_METHOD3(read, std::vector<uint8_t>(uint16_t, uint32_t, uint32_t)); + MOCK_METHOD3(write, bool(uint16_t, uint32_t, const std::vector<uint8_t>&)); + MOCK_METHOD1(deleteBlob, bool(const std::string&)); +}; +} // namespace blobs diff --git a/test/manager_open_unittest.cpp b/test/manager_open_unittest.cpp new file mode 100644 index 0000000..309d3f6 --- /dev/null +++ b/test/manager_open_unittest.cpp @@ -0,0 +1,85 @@ +#include "blob_mock.hpp" +#include "manager.hpp" + +#include <string> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::_; +using ::testing::Return; + +TEST(ManagerOpenTest, OpenButNoHandler) +{ + // No handler claims to be able to open the file. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = OpenFlags::read, sess; + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(false)); + EXPECT_FALSE(mgr.open(flags, path, &sess)); +} + +TEST(ManagerOpenTest, OpenButHandlerFailsOpen) +{ + // The handler is found but Open fails. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = OpenFlags::read, sess; + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(false)); + EXPECT_FALSE(mgr.open(flags, path, &sess)); +} + +TEST(ManagerOpenTest, OpenFailsMustSupplyAtLeastReadOrWriteFlag) +{ + // One must supply either read or write in the flags for the session to + // open. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = 0, sess; + std::string path = "/asdf/asdf"; + + /* It checks if someone can handle the blob before it checks the flags. */ + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + + EXPECT_FALSE(mgr.open(flags, path, &sess)); +} + +TEST(ManagerOpenTest, OpenSucceeds) +{ + // The handler is found and Open succeeds. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = OpenFlags::read, sess; + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true)); + EXPECT_TRUE(mgr.open(flags, path, &sess)); + + // TODO(venture): Need a way to verify the session is associated with it, + // maybe just call Read() or SessionStat() +} +} // namespace blobs diff --git a/test/manager_read_unittest.cpp b/test/manager_read_unittest.cpp new file mode 100644 index 0000000..1d40f5d --- /dev/null +++ b/test/manager_read_unittest.cpp @@ -0,0 +1,78 @@ +#include "blob_mock.hpp" +#include "manager.hpp" + +#include <vector> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::_; +using ::testing::Return; + +TEST(ManagerReadTest, ReadNoSessionReturnsFalse) +{ + // Calling Read on a session that doesn't exist should return false. + + BlobManager mgr; + uint16_t sess = 1; + uint32_t ofs = 0x54; + uint32_t requested = 0x100; + + std::vector<uint8_t> result = mgr.read(sess, ofs, requested); + EXPECT_EQ(0, result.size()); +} + +TEST(ManagerReadTest, ReadFromWriteOnlyFails) +{ + // The session manager will not route a Read call to a blob if the session + // was opened as write-only. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t sess = 1; + uint32_t ofs = 0x54; + uint32_t requested = 0x100; + uint16_t flags = OpenFlags::write; + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true)); + EXPECT_TRUE(mgr.open(flags, path, &sess)); + + std::vector<uint8_t> result = mgr.read(sess, ofs, requested); + EXPECT_EQ(0, result.size()); +} + +TEST(ManagerReadTest, ReadFromHandlerReturnsData) +{ + // There is no logic in this as it's just as a pass-thru command, however + // we want to verify this behavior doesn't change. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t sess = 1; + uint32_t ofs = 0x54; + uint32_t requested = 0x100; + uint16_t flags = OpenFlags::read; + std::string path = "/asdf/asdf"; + std::vector<uint8_t> data = {0x12, 0x14, 0x15, 0x16}; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true)); + EXPECT_TRUE(mgr.open(flags, path, &sess)); + + EXPECT_CALL(*m1ptr, read(sess, ofs, requested)).WillOnce(Return(data)); + + std::vector<uint8_t> result = mgr.read(sess, ofs, requested); + EXPECT_EQ(data.size(), result.size()); + EXPECT_EQ(result, data); +} +} // namespace blobs diff --git a/test/manager_sessionstat_unittest.cpp b/test/manager_sessionstat_unittest.cpp new file mode 100644 index 0000000..6ae27d3 --- /dev/null +++ b/test/manager_sessionstat_unittest.cpp @@ -0,0 +1,66 @@ +#include "blob_mock.hpp" +#include "manager.hpp" + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::_; +using ::testing::Return; + +TEST(ManagerSessionStatTest, StatNoSessionReturnsFalse) +{ + // Calling Stat on a session that doesn't exist should return false. + + BlobManager mgr; + struct BlobMeta meta; + uint16_t sess = 1; + + EXPECT_FALSE(mgr.stat(sess, &meta)); +} + +TEST(ManagerSessionStatTest, StatSessionFoundButHandlerReturnsFalse) +{ + // The handler was found but it returned failure. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = OpenFlags::read, sess; + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true)); + EXPECT_TRUE(mgr.open(flags, path, &sess)); + + struct BlobMeta meta; + EXPECT_CALL(*m1ptr, stat(sess, &meta)).WillOnce(Return(false)); + + EXPECT_FALSE(mgr.stat(sess, &meta)); +} + +TEST(ManagerSessionStatTest, StatSessionFoundAndHandlerReturnsSuccess) +{ + // The handler was found and returned success. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = OpenFlags::read, sess; + std::string path = "/asdf/asdf"; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true)); + EXPECT_TRUE(mgr.open(flags, path, &sess)); + + struct BlobMeta meta; + EXPECT_CALL(*m1ptr, stat(sess, &meta)).WillOnce(Return(true)); + + EXPECT_TRUE(mgr.stat(sess, &meta)); +} +} // namespace blobs diff --git a/test/manager_stat_unittest.cpp b/test/manager_stat_unittest.cpp new file mode 100644 index 0000000..a13a66d --- /dev/null +++ b/test/manager_stat_unittest.cpp @@ -0,0 +1,60 @@ +#include "blob_mock.hpp" +#include "manager.hpp" + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::Return; + +TEST(ManagerStatTest, StatNoHandler) +{ + // There is no handler for this path. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + struct BlobMeta meta; + std::string path = "/asdf/asdf"; + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(false)); + + EXPECT_FALSE(mgr.stat(path, &meta)); +} + +TEST(ManagerStatTest, StatHandlerFoundButFails) +{ + // There is a handler for this path but Stat fails. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + struct BlobMeta meta; + std::string path = "/asdf/asdf"; + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, stat(path, &meta)).WillOnce(Return(false)); + + EXPECT_FALSE(mgr.stat(path, &meta)); +} + +TEST(ManagerStatTest, StatHandlerFoundAndSucceeds) +{ + // There is a handler and Stat succeeds. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + struct BlobMeta meta; + std::string path = "/asdf/asdf"; + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, stat(path, &meta)).WillOnce(Return(true)); + + EXPECT_TRUE(mgr.stat(path, &meta)); +} +} // namespace blobs diff --git a/test/manager_unittest.cpp b/test/manager_unittest.cpp new file mode 100644 index 0000000..7d4d49e --- /dev/null +++ b/test/manager_unittest.cpp @@ -0,0 +1,172 @@ +#include "blob_mock.hpp" +#include "manager.hpp" + +#include <algorithm> +#include <string> +#include <vector> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::Return; + +TEST(BlobsTest, RegisterNullPointerFails) +{ + // The only invalid pointer really is a null one. + + BlobManager mgr; + EXPECT_FALSE(mgr.registerHandler(nullptr)); +} + +TEST(BlobsTest, RegisterNonNullPointerPasses) +{ + // Test that the valid pointer is boringly registered. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); +} + +TEST(BlobsTest, GetCountNoBlobsRegistered) +{ + // Request the Blob Count when there are no blobs. + + BlobManager mgr; + EXPECT_EQ(0, mgr.buildBlobList()); +} + +TEST(BlobsTest, GetCountBlobRegisteredReturnsOne) +{ + // Request the blob count and verify the list is of length one. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + std::vector<std::string> v = {"item"}; + + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + // We expect it to ask for the list. + EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v)); + + EXPECT_EQ(1, mgr.buildBlobList()); +} + +TEST(BlobsTest, GetCountBlobsRegisteredEachReturnsOne) +{ + // Request the blob count and verify the list is of length two. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + std::unique_ptr<BlobMock> m2 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + auto m2ptr = m2.get(); + std::vector<std::string> v1, v2; + + v1.push_back("asdf"); + v2.push_back("ghjk"); + + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + EXPECT_TRUE(mgr.registerHandler(std::move(m2))); + + // We expect it to ask for the list. + EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v1)); + EXPECT_CALL(*m2ptr, getBlobIds()).WillOnce(Return(v2)); + + EXPECT_EQ(2, mgr.buildBlobList()); +} + +TEST(BlobsTest, EnumerateBlobZerothEntry) +{ + // Validate that you can read back the 0th blobId. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + std::unique_ptr<BlobMock> m2 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + auto m2ptr = m2.get(); + std::vector<std::string> v1, v2; + + v1.push_back("asdf"); + v2.push_back("ghjk"); + + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + EXPECT_TRUE(mgr.registerHandler(std::move(m2))); + + // We expect it to ask for the list. + EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v1)); + EXPECT_CALL(*m2ptr, getBlobIds()).WillOnce(Return(v2)); + + EXPECT_EQ(2, mgr.buildBlobList()); + + std::string result = mgr.getBlobId(0); + // The exact order the blobIds is returned is not guaranteed to never + // change. + EXPECT_TRUE("asdf" == result || "ghjk" == result); +} + +TEST(BlobsTest, EnumerateBlobFirstEntry) +{ + // Validate you can read back the two real entries. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + std::unique_ptr<BlobMock> m2 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + auto m2ptr = m2.get(); + std::vector<std::string> v1, v2; + + v1.push_back("asdf"); + v2.push_back("ghjk"); + + // Presently the list of blobs is read and appended in a specific order, + // but I don't want to rely on that. + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + EXPECT_TRUE(mgr.registerHandler(std::move(m2))); + + // We expect it to ask for the list. + EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v1)); + EXPECT_CALL(*m2ptr, getBlobIds()).WillOnce(Return(v2)); + + EXPECT_EQ(2, mgr.buildBlobList()); + + // Try to grab the two blobIds and verify they're in the list. + std::vector<std::string> results; + results.push_back(mgr.getBlobId(0)); + results.push_back(mgr.getBlobId(1)); + EXPECT_EQ(2, results.size()); + EXPECT_TRUE(std::find(results.begin(), results.end(), "asdf") != + results.end()); + EXPECT_TRUE(std::find(results.begin(), results.end(), "ghjk") != + results.end()); +} + +TEST(BlobTest, EnumerateBlobInvalidEntry) +{ + // Validate trying to read an invalid entry fails expectedly. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + std::unique_ptr<BlobMock> m2 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + auto m2ptr = m2.get(); + std::vector<std::string> v1, v2; + + v1.push_back("asdf"); + v2.push_back("ghjk"); + + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + EXPECT_TRUE(mgr.registerHandler(std::move(m2))); + + // We expect it to ask for the list. + EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v1)); + EXPECT_CALL(*m2ptr, getBlobIds()).WillOnce(Return(v2)); + + EXPECT_EQ(2, mgr.buildBlobList()); + + // Grabs the third entry which isn't valid. + EXPECT_STREQ("", mgr.getBlobId(2).c_str()); +} +} // namespace blobs diff --git a/test/manager_write_unittest.cpp b/test/manager_write_unittest.cpp new file mode 100644 index 0000000..33c6d5a --- /dev/null +++ b/test/manager_write_unittest.cpp @@ -0,0 +1,90 @@ +#include "blob_mock.hpp" +#include "manager.hpp" + +#include <gtest/gtest.h> + +using ::testing::_; +using ::testing::Return; + +namespace blobs +{ + +TEST(ManagerWriteTest, WriteNoSessionReturnsFalse) +{ + // Calling Write on a session that doesn't exist should return false. + + BlobManager mgr; + uint16_t sess = 1; + uint32_t ofs = 0x54; + std::vector<uint8_t> data = {0x11, 0x22}; + + EXPECT_FALSE(mgr.write(sess, ofs, data)); +} + +TEST(ManagerWriteTest, WriteSessionFoundButHandlerReturnsFalse) +{ + // The handler was found but it returned failure. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = OpenFlags::write, sess; + std::string path = "/asdf/asdf"; + uint32_t ofs = 0x54; + std::vector<uint8_t> data = {0x11, 0x22}; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true)); + EXPECT_TRUE(mgr.open(flags, path, &sess)); + + EXPECT_CALL(*m1ptr, write(sess, ofs, data)).WillOnce(Return(false)); + + EXPECT_FALSE(mgr.write(sess, ofs, data)); +} + +TEST(ManagerWriteTest, WriteFailsBecauseFileOpenedReadOnly) +{ + // The manager will not route a write call to a file opened read-only. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = OpenFlags::read, sess; + std::string path = "/asdf/asdf"; + uint32_t ofs = 0x54; + std::vector<uint8_t> data = {0x11, 0x22}; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true)); + EXPECT_TRUE(mgr.open(flags, path, &sess)); + + EXPECT_FALSE(mgr.write(sess, ofs, data)); +} + +TEST(ManagerWriteTest, WriteSessionFoundAndHandlerReturnsSuccess) +{ + // The handler was found and returned success. + + BlobManager mgr; + std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>(); + auto m1ptr = m1.get(); + EXPECT_TRUE(mgr.registerHandler(std::move(m1))); + + uint16_t flags = OpenFlags::write, sess; + std::string path = "/asdf/asdf"; + uint32_t ofs = 0x54; + std::vector<uint8_t> data = {0x11, 0x22}; + + EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true)); + EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true)); + EXPECT_TRUE(mgr.open(flags, path, &sess)); + + EXPECT_CALL(*m1ptr, write(sess, ofs, data)).WillOnce(Return(true)); + + EXPECT_TRUE(mgr.write(sess, ofs, data)); +} +} // namespace blobs diff --git a/test/process_unittest.cpp b/test/process_unittest.cpp new file mode 100644 index 0000000..2ffb023 --- /dev/null +++ b/test/process_unittest.cpp @@ -0,0 +1,290 @@ +#include "crc.hpp" +#include "crc_mock.hpp" +#include "ipmi.hpp" +#include "manager_mock.hpp" +#include "process.hpp" + +#include <cstring> + +#include <gtest/gtest.h> + +namespace blobs +{ + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrictMock; + +// ipmid.hpp isn't installed where we can grab it and this value is per BMC +// SoC. +#define MAX_IPMI_BUFFER 64 + +namespace +{ + +void EqualFunctions(IpmiBlobHandler lhs, IpmiBlobHandler rhs) +{ + EXPECT_FALSE(lhs == nullptr); + EXPECT_FALSE(rhs == nullptr); + + ipmi_ret_t (*const* lPtr)(ManagerInterface*, const uint8_t*, uint8_t*, + size_t*) = + lhs.target<ipmi_ret_t (*)(ManagerInterface*, const uint8_t*, uint8_t*, + size_t*)>(); + + ipmi_ret_t (*const* rPtr)(ManagerInterface*, const uint8_t*, uint8_t*, + size_t*) = + rhs.target<ipmi_ret_t (*)(ManagerInterface*, const uint8_t*, uint8_t*, + size_t*)>(); + + EXPECT_TRUE(lPtr); + EXPECT_TRUE(rPtr); + EXPECT_EQ(*lPtr, *rPtr); + return; +} + +} // namespace + +TEST(ValidateBlobCommandTest, InvalidCommandReturnsFailure) +{ + // Verify we handle an invalid command. + + StrictMock<CrcMock> crc; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + + request[0] = 0xff; // There is no command 0xff. + dataLen = sizeof(uint8_t); // There is no payload for CRC. + + EXPECT_EQ(nullptr, validateBlobCommand(&crc, request, reply, &dataLen)); +} + +TEST(ValidateBlobCommandTest, ValidCommandWithoutPayload) +{ + // Verify we handle a valid command that doesn't have a payload. + + StrictMock<CrcMock> crc; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + + request[0] = BlobOEMCommands::bmcBlobGetCount; + dataLen = sizeof(uint8_t); // There is no payload for CRC. + + IpmiBlobHandler res = validateBlobCommand(&crc, request, reply, &dataLen); + EXPECT_FALSE(res == nullptr); + EqualFunctions(getBlobCount, res); +} + +TEST(ValidateBlobCommandTest, WithPayloadMinimumLengthIs3VerifyChecks) +{ + // Verify that if there's a payload, it's at least one command byte and + // two bytes for the crc16 and then one data byte. + + StrictMock<CrcMock> crc; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + + request[0] = BlobOEMCommands::bmcBlobGetCount; + dataLen = sizeof(uint8_t) + sizeof(uint16_t); + // There is a payload, but there are insufficient bytes. + + EXPECT_EQ(nullptr, validateBlobCommand(&crc, request, reply, &dataLen)); +} + +TEST(ValidateBlobCommandTest, WithPayloadAndInvalidCrc) +{ + // Verify that the CRC is checked, and failure is reported. + + StrictMock<CrcMock> crc; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + + auto req = reinterpret_cast<struct BmcBlobWriteTx*>(request); + req->cmd = BlobOEMCommands::bmcBlobWrite; + req->crc = 0x34; + req->sessionId = 0x54; + req->offset = 0x100; + + uint8_t expectedBytes[2] = {0x66, 0x67}; + std::memcpy(req->data, &expectedBytes[0], sizeof(expectedBytes)); + + dataLen = sizeof(struct BmcBlobWriteTx) + sizeof(expectedBytes); + + // skip over cmd and crc. + size_t expectedLen = dataLen - 3; + + EXPECT_CALL(crc, clear()); + EXPECT_CALL(crc, compute(_, expectedLen)) + .WillOnce(Invoke([&](const uint8_t* bytes, uint32_t length) { + EXPECT_EQ(0, std::memcmp(&request[3], bytes, length)); + })); + EXPECT_CALL(crc, get()).WillOnce(Return(0x1234)); + + EXPECT_EQ(nullptr, validateBlobCommand(&crc, request, reply, &dataLen)); +} + +TEST(ValidateBlobCommandTest, WithPayloadAndValidCrc) +{ + // Verify the CRC is checked and if it matches, return the handler. + + StrictMock<CrcMock> crc; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + + auto req = reinterpret_cast<struct BmcBlobWriteTx*>(request); + req->cmd = BlobOEMCommands::bmcBlobWrite; + req->crc = 0x3412; + req->sessionId = 0x54; + req->offset = 0x100; + + uint8_t expectedBytes[2] = {0x66, 0x67}; + std::memcpy(req->data, &expectedBytes[0], sizeof(expectedBytes)); + + dataLen = sizeof(struct BmcBlobWriteTx) + sizeof(expectedBytes); + + // skip over cmd and crc. + size_t expectedLen = dataLen - 3; + + EXPECT_CALL(crc, clear()); + EXPECT_CALL(crc, compute(_, expectedLen)) + .WillOnce(Invoke([&](const uint8_t* bytes, uint32_t length) { + EXPECT_EQ(0, std::memcmp(&request[3], bytes, length)); + })); + EXPECT_CALL(crc, get()).WillOnce(Return(0x3412)); + + IpmiBlobHandler res = validateBlobCommand(&crc, request, reply, &dataLen); + EXPECT_FALSE(res == nullptr); + EqualFunctions(writeBlob, res); +} + +TEST(ValidateBlobCommandTest, InputIntegrationTest) +{ + // Given a request buffer generated by the host-side utility, verify it is + // properly routed. + + Crc16 crc; + size_t dataLen; + uint8_t request[] = {0x02, 0x88, 0x21, 0x03, 0x00, 0x2f, 0x64, 0x65, 0x76, + 0x2f, 0x68, 0x61, 0x76, 0x65, 0x6e, 0x2f, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x73, + 0x73, 0x74, 0x68, 0x72, 0x75, 0x00}; + + // The above request to open a file for reading & writing named: + // "/dev/haven/command_passthru" + + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + + dataLen = sizeof(request); + + IpmiBlobHandler res = validateBlobCommand(&crc, request, reply, &dataLen); + EXPECT_FALSE(res == nullptr); + EqualFunctions(openBlob, res); +} + +TEST(ProcessBlobCommandTest, CommandReturnsNotOk) +{ + // Verify that if the IPMI command handler returns not OK that this is + // noticed and returned. + + StrictMock<CrcMock> crc; + StrictMock<ManagerMock> manager; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + + IpmiBlobHandler h = [](ManagerInterface* mgr, const uint8_t* reqBuf, + uint8_t* replyCmdBuf, + size_t* dataLen) { return IPMI_CC_INVALID; }; + + dataLen = sizeof(request); + + EXPECT_EQ(IPMI_CC_INVALID, + processBlobCommand(h, &manager, &crc, request, reply, &dataLen)); +} + +TEST(ProcessBlobCommandTest, CommandReturnsOkWithNoPayload) +{ + // Verify that if the IPMI command handler returns OK but without a payload + // it doesn't try to compute a CRC. + + StrictMock<CrcMock> crc; + StrictMock<ManagerMock> manager; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + + IpmiBlobHandler h = [](ManagerInterface* mgr, const uint8_t* reqBuf, + uint8_t* replyCmdBuf, size_t* dataLen) { + (*dataLen) = 0; + return IPMI_CC_OK; + }; + + dataLen = sizeof(request); + + EXPECT_EQ(IPMI_CC_OK, + processBlobCommand(h, &manager, &crc, request, reply, &dataLen)); +} + +TEST(ProcessBlobCommandTest, CommandReturnsOkWithInvalidPayloadLength) +{ + // There is a minimum payload length of 3 bytes, this command returns a + // payload of 2 bytes. + + StrictMock<CrcMock> crc; + StrictMock<ManagerMock> manager; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + + IpmiBlobHandler h = [](ManagerInterface* mgr, const uint8_t* reqBuf, + uint8_t* replyCmdBuf, size_t* dataLen) { + (*dataLen) = sizeof(uint16_t); + return IPMI_CC_OK; + }; + + dataLen = sizeof(request); + + EXPECT_EQ(IPMI_CC_INVALID, + processBlobCommand(h, &manager, &crc, request, reply, &dataLen)); +} + +TEST(ProcessBlobCommandTest, CommandReturnsOkWithValidPayloadLength) +{ + // There is a minimum payload length of 3 bytes, this command returns a + // payload of 3 bytes and the crc code is called to process the payload. + + StrictMock<CrcMock> crc; + StrictMock<ManagerMock> manager; + size_t dataLen; + uint8_t request[MAX_IPMI_BUFFER] = {0}; + uint8_t reply[MAX_IPMI_BUFFER] = {0}; + uint32_t payloadLen = sizeof(uint16_t) + sizeof(uint8_t); + + IpmiBlobHandler h = [payloadLen](ManagerInterface* mgr, + const uint8_t* reqBuf, + uint8_t* replyCmdBuf, size_t* dataLen) { + (*dataLen) = payloadLen; + replyCmdBuf[2] = 0x56; + return IPMI_CC_OK; + }; + + dataLen = sizeof(request); + + EXPECT_CALL(crc, clear()); + EXPECT_CALL(crc, compute(_, payloadLen)); + EXPECT_CALL(crc, get()).WillOnce(Return(0x3412)); + + EXPECT_EQ(IPMI_CC_OK, + processBlobCommand(h, &manager, &crc, request, reply, &dataLen)); + EXPECT_EQ(dataLen, payloadLen); + + uint8_t expectedBytes[3] = {0x12, 0x34, 0x56}; + EXPECT_EQ(0, std::memcmp(expectedBytes, reply, sizeof(expectedBytes))); +} +} // namespace blobs |