From 9d2d94d22b2b51a38ddf3bb651ff16076c9dad36 Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Thu, 24 Feb 2022 23:40:09 -0500 Subject: [PATCH] Tests: Add disk driver tests --- CMakeLists.txt | 2 + test/diskdriverreadtest.cpp | 55 +++++++++++++++ test/diskdrivertest.h | 125 +++++++++++++++++++++++++++++++++++ test/diskdriverwritetest.cpp | 109 ++++++++++++++++++++++++++++++ 4 files changed, 291 insertions(+) create mode 100644 test/diskdriverreadtest.cpp create mode 100644 test/diskdrivertest.h create mode 100644 test/diskdriverwritetest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b852e0..71afa00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -317,6 +317,8 @@ if(LIBICSNEO_BUILD_TESTS) add_executable(libicsneo-tests test/main.cpp + test/diskdriverreadtest.cpp + test/diskdriverwritetest.cpp test/eventmanagertest.cpp test/ethernetpacketizertest.cpp ) diff --git a/test/diskdriverreadtest.cpp b/test/diskdriverreadtest.cpp new file mode 100644 index 0000000..1449f20 --- /dev/null +++ b/test/diskdriverreadtest.cpp @@ -0,0 +1,55 @@ +#include "diskdrivertest.h" + +TEST_F(DiskDriverTest, Read) { + std::array buf; + buf.fill(0u); + const auto amountRead = readLogicalDisk(0, buf.data(), buf.size()); + EXPECT_TRUE(amountRead.has_value()); + EXPECT_EQ(amountRead, buf.size()); + EXPECT_EQ(buf[0], TEST_STRING[0]); + EXPECT_EQ(buf[126], 126u); + EXPECT_EQ(driver->readCalls, 1u); +} + +TEST_F(DiskDriverTest, ReadUnaligned) { + std::array buf; + buf.fill(0u); + const auto amountRead = readLogicalDisk(1, buf.data(), buf.size()); + EXPECT_TRUE(amountRead.has_value()); + EXPECT_EQ(amountRead, buf.size()); + EXPECT_EQ(buf[0], TEST_STRING[1]); + EXPECT_EQ(buf[110], 111u); + EXPECT_EQ(driver->readCalls, 1u); +} + +TEST_F(DiskDriverTest, ReadUnalignedLong) { + std::array buf; + buf.fill(0u); + const auto amountRead = readLogicalDisk(300, buf.data(), buf.size()); + EXPECT_TRUE(amountRead.has_value()); + EXPECT_EQ(amountRead, buf.size()); + EXPECT_EQ(buf[0], 300 & 0xFF); + EXPECT_EQ(buf[110], 410 & 0xFF); + EXPECT_EQ(driver->readCalls, 3u); +} + +TEST_F(DiskDriverTest, ReadPastEnd) { + std::array buf; + buf.fill(0u); + expectedErrors.push({ APIEvent::Type::EOFReached, APIEvent::Severity::Error }); + const auto amountRead = readLogicalDisk(1000, buf.data(), buf.size()); + EXPECT_TRUE(amountRead.has_value()); + EXPECT_EQ(amountRead, 24u); + EXPECT_EQ(buf[0], 1000 & 0xFF); + EXPECT_EQ(buf[23], 1023 & 0xFF); + EXPECT_EQ(driver->readCalls, 2u); // One for the read, another to check EOF +} + +TEST_F(DiskDriverTest, ReadBadStartingPos) { + std::array buf; + buf.fill(0u); + expectedErrors.push({ APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error }); + const auto amountRead = readLogicalDisk(2000, buf.data(), buf.size()); + EXPECT_FALSE(amountRead.has_value()); + EXPECT_EQ(driver->readCalls, 1u); // One to check EOF +} \ No newline at end of file diff --git a/test/diskdrivertest.h b/test/diskdrivertest.h new file mode 100644 index 0000000..f120a1d --- /dev/null +++ b/test/diskdrivertest.h @@ -0,0 +1,125 @@ +#ifndef __DISKDRIVERTEST_H_ +#define __DISKDRIVERTEST_H_ + +#include "icsneo/disk/diskreaddriver.h" +#include "icsneo/disk/diskwritedriver.h" +#include "icsneo/platform/optional.h" +#include "gtest/gtest.h" +#include +#include + +using namespace icsneo; + +#define TEST_STRING "The quick brown fox jumps over the lazy dog." +#define TEST_OVERWRITE_STRING "test fun" + +class MockDiskDriver : public Disk::ReadDriver, public Disk::WriteDriver { +public: + Disk::Access getAccess() const override { return Disk::Access::EntireCard; } + std::pair getBlockSizeBounds() const override { return { 8, 256 }; } + + optional readLogicalDiskAligned(Communication&, device_eventhandler_t, + uint64_t pos, uint8_t* into, uint64_t amount, std::chrono::milliseconds) override { + readCalls++; + + EXPECT_EQ(pos % getBlockSizeBounds().first, 0); // Ensure the alignment rules are respected + EXPECT_LE(amount, getBlockSizeBounds().second); + EXPECT_EQ(amount % getBlockSizeBounds().first, 0); + + if(pos > mockDisk.size()) // EOF + return nullopt; + + optional readAmount = std::min(amount, mockDisk.size() - pos); + if(readAmount > 0u) + memcpy(into, mockDisk.data() + pos, static_cast(*readAmount)); + + // So that the test can mess with atomicity + if(afterReadHook) + afterReadHook(); + + return readAmount; + } + + optional writeLogicalDiskAligned(Communication&, device_eventhandler_t report, uint64_t pos, + const uint8_t* atomicBuf, const uint8_t* from, uint64_t amount, std::chrono::milliseconds) override { + writeCalls++; + + EXPECT_EQ(pos % getBlockSizeBounds().first, 0); // Ensure the alignment rules are respected + EXPECT_LE(amount, getBlockSizeBounds().second); + EXPECT_EQ(amount % getBlockSizeBounds().first, 0); + + if(pos > mockDisk.size()) // EOF + return nullopt; + + optional writeAmount = std::min(amount, mockDisk.size() - pos); + if(writeAmount > 0u) { + if(atomicBuf) { + if(supportsAtomic) { + atomicityChecks++; + if(memcmp(mockDisk.data() + pos, atomicBuf, static_cast(*writeAmount))) + return RetryAtomic; // Atomic check failed + } else { + report(APIEvent::Type::AtomicOperationCompletedNonatomically, NonatomicSeverity); + } + } + + memcpy(mockDisk.data() + pos, from, static_cast(*writeAmount)); + } + return writeAmount; + } + + std::array mockDisk; + size_t readCalls = 0; + size_t writeCalls = 0; + size_t atomicityChecks = 0; + bool supportsAtomic = true; // Ability to simulate a driver that doesn't support atomic writes + std::function afterReadHook; +}; + +class DiskDriverTest : public ::testing::Test { +protected: + // Start with a clean instance of MockDiskDriver for every test + void SetUp() override { + onError = [this](APIEvent::Type t, APIEvent::Severity s) { + if(expectedErrors.empty()) { + // Unless caught by the test, the driver should not throw errors + EXPECT_TRUE(false); + } else { + const auto expected = expectedErrors.front(); + expectedErrors.pop(); + EXPECT_EQ(expected.first, t); + if(expected.second != APIEvent::Severity::Any) { + EXPECT_EQ(expected.second, s); + } + } + }; + driver.emplace(); + + // Populate with some fake data + memcpy(driver->mockDisk.data(), TEST_STRING, sizeof(TEST_STRING)); + for (size_t i = sizeof(TEST_STRING); i < driver->mockDisk.size(); i++) + driver->mockDisk[i] = uint8_t(i & 0xFF); + } + + void TearDown() override { + driver.reset(); + } + + optional readLogicalDisk(uint64_t pos, uint8_t* into, uint64_t amount) { + return driver->readLogicalDisk(*com, onError, pos, into, amount /* default timeout */); + } + + optional writeLogicalDisk(uint64_t pos, const uint8_t* from, uint64_t amount) { + return driver->writeLogicalDisk(*com, onError, *driver, pos, from, amount /* default timeout */); + } + + optional driver; + + std::queue< std::pair > expectedErrors; + device_eventhandler_t onError; + + // We will dereference this but the driver base should never access it + Communication* const com = nullptr; +}; + +#endif // __DISKDRIVERTEST_H_ \ No newline at end of file diff --git a/test/diskdriverwritetest.cpp b/test/diskdriverwritetest.cpp new file mode 100644 index 0000000..493e14c --- /dev/null +++ b/test/diskdriverwritetest.cpp @@ -0,0 +1,109 @@ +#include "diskdrivertest.h" + +TEST_F(DiskDriverTest, Write) { + const auto amountWritten = writeLogicalDisk(0u, reinterpret_cast(TEST_OVERWRITE_STRING), sizeof(TEST_OVERWRITE_STRING)); + EXPECT_TRUE(amountWritten.has_value()); + EXPECT_EQ(amountWritten, sizeof(TEST_OVERWRITE_STRING)); + EXPECT_STREQ(reinterpret_cast(driver->mockDisk.data()), TEST_OVERWRITE_STRING); + EXPECT_EQ(driver->mockDisk[sizeof(TEST_OVERWRITE_STRING) + 1], TEST_STRING[sizeof(TEST_OVERWRITE_STRING) + 1]); + EXPECT_EQ(driver->mockDisk[126], 126u); + EXPECT_EQ(driver->atomicityChecks, 1u); + EXPECT_EQ(driver->readCalls, 1u); + EXPECT_EQ(driver->writeCalls, 1u); +} + +TEST_F(DiskDriverTest, WriteNoAtomicityCheck) { + driver->supportsAtomic = false; + expectedErrors.push({ APIEvent::Type::AtomicOperationCompletedNonatomically, APIEvent::Severity::EventInfo }); + const auto amountWritten = writeLogicalDisk(0u, reinterpret_cast(TEST_OVERWRITE_STRING), sizeof(TEST_OVERWRITE_STRING)); + EXPECT_TRUE(amountWritten.has_value()); + EXPECT_EQ(amountWritten, sizeof(TEST_OVERWRITE_STRING)); + EXPECT_STREQ(reinterpret_cast(driver->mockDisk.data()), TEST_OVERWRITE_STRING); + EXPECT_EQ(driver->mockDisk[sizeof(TEST_OVERWRITE_STRING) + 1], TEST_STRING[sizeof(TEST_OVERWRITE_STRING) + 1]); + EXPECT_EQ(driver->mockDisk[126], 126u); + EXPECT_EQ(driver->atomicityChecks, 0u); + EXPECT_EQ(driver->readCalls, 1u); + EXPECT_EQ(driver->writeCalls, 1u); +} + +TEST_F(DiskDriverTest, WriteUnaligned) { + const auto amountWritten = writeLogicalDisk(3, reinterpret_cast(TEST_OVERWRITE_STRING), sizeof(TEST_OVERWRITE_STRING)); + EXPECT_TRUE(amountWritten.has_value()); + EXPECT_EQ(amountWritten, sizeof(TEST_OVERWRITE_STRING)); + EXPECT_EQ(driver->mockDisk[0], TEST_STRING[0]); + EXPECT_EQ(driver->mockDisk[5], TEST_OVERWRITE_STRING[2]); + EXPECT_EQ(driver->mockDisk[110], 110u); + EXPECT_EQ(driver->atomicityChecks, 1u); + EXPECT_EQ(driver->readCalls, 1u); + EXPECT_EQ(driver->writeCalls, 1u); +} + +TEST_F(DiskDriverTest, WriteUnalignedLong) { + std::array buf; + for(size_t i = 0; i < buf.size(); i++) + buf[i] = static_cast((buf.size() - i) + 20); + const auto amountWritten = writeLogicalDisk(300, buf.data(), buf.size()); + EXPECT_TRUE(amountWritten.has_value()); + EXPECT_EQ(amountWritten, buf.size()); + EXPECT_EQ(driver->mockDisk[0], TEST_STRING[0]); + EXPECT_EQ(driver->mockDisk[330], ((buf.size() - 30) + 20) & 0xFF); + EXPECT_EQ(driver->atomicityChecks, 3u); + EXPECT_EQ(driver->readCalls, 3u); + EXPECT_EQ(driver->writeCalls, 3u); +} + +TEST_F(DiskDriverTest, WriteUnalignedLongAtomicityFailures) { + std::array buf; + for(size_t i = 0; i < buf.size(); i++) + buf[i] = static_cast((buf.size() - i) + 20); + for(int i = 0; i < 4; i++) + expectedErrors.push({ APIEvent::Type::AtomicOperationRetried, APIEvent::Severity::EventInfo }); + + int i = 0; + driver->afterReadHook = [&i, this]() { + switch(i) { + case 0: driver->mockDisk[295] = uint8_t(0xCD); break; + case 1: break; // We don't mess with this one so the first block can be written + case 2: driver->mockDisk[600] = uint8_t(0xDC); break; + case 3: driver->mockDisk[602] = uint8_t(0xDC); break; + case 4: break; // We don't mess with this one so the second block can be written + case 5: driver->mockDisk[777] = uint8_t(0x22); break; + case 6: break; // We don't mess with this one so the third block can be written + } + i++; + }; + + const auto amountWritten = writeLogicalDisk(300, buf.data(), buf.size()); + EXPECT_TRUE(amountWritten.has_value()); + EXPECT_EQ(amountWritten, buf.size()); + EXPECT_EQ(driver->mockDisk[0], TEST_STRING[0]); + EXPECT_EQ(driver->mockDisk[295], 0xCDu); // If the atomic worked correctly this write won't have gotten trampled + // Our writes happen after both of these, so they overwrite the 0xDC values + EXPECT_EQ(driver->mockDisk[600], ((buf.size() - 300) + 20) & 0xFF); + EXPECT_EQ(driver->mockDisk[602], ((buf.size() - 302) + 20) & 0xFF); + + EXPECT_EQ(driver->atomicityChecks, 7u); + EXPECT_EQ(driver->readCalls, 7u); + EXPECT_EQ(driver->writeCalls, 7u); +} + +TEST_F(DiskDriverTest, WritePastEnd) { + expectedErrors.push({ APIEvent::Type::EOFReached, APIEvent::Severity::Error }); + const auto amountWritten = writeLogicalDisk(1020, reinterpret_cast(TEST_OVERWRITE_STRING), sizeof(TEST_OVERWRITE_STRING)); + EXPECT_TRUE(amountWritten.has_value()); + EXPECT_EQ(amountWritten, 4u); + EXPECT_EQ(driver->mockDisk[0], TEST_STRING[0]); + EXPECT_EQ(driver->mockDisk[1019], 1019 & 0xFF); + EXPECT_EQ(driver->mockDisk[1020], TEST_OVERWRITE_STRING[0]); + EXPECT_EQ(driver->mockDisk[1023], TEST_OVERWRITE_STRING[3]); + EXPECT_EQ(driver->writeCalls, 1u); // One for the write, another to check EOF +} + +TEST_F(DiskDriverTest, WriteBadStartingPos) { + expectedErrors.push({ APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error }); + const auto amountWritten = writeLogicalDisk(2000, reinterpret_cast(TEST_OVERWRITE_STRING), sizeof(TEST_OVERWRITE_STRING)); + EXPECT_FALSE(amountWritten.has_value()); + EXPECT_EQ(driver->atomicityChecks, 0u); + EXPECT_EQ(driver->readCalls, 1u); + EXPECT_EQ(driver->writeCalls, 0u); // We never even attempt the write +} \ No newline at end of file