From 605e2590f183ffa2b4883f6eb31c3d1b6d5cda1c Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Mon, 12 Oct 2020 12:10:34 -0700 Subject: [PATCH] buffered writer - progress on #1463 (#1875) * add buffered writer * rename --- firmware/controllers/system/buffered_writer.h | 63 ++++++++++++++ unit_tests/tests/tests.mk | 1 + .../tests/util/test_buffered_writer.cpp | 85 +++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 firmware/controllers/system/buffered_writer.h create mode 100644 unit_tests/tests/util/test_buffered_writer.cpp diff --git a/firmware/controllers/system/buffered_writer.h b/firmware/controllers/system/buffered_writer.h new file mode 100644 index 0000000000..709a7a5693 --- /dev/null +++ b/firmware/controllers/system/buffered_writer.h @@ -0,0 +1,63 @@ +#pragma once + +#include + +struct Writer { + virtual size_t write(const char* buffer, size_t count) = 0; + virtual size_t flush() = 0; +}; + +template +class BufferedWriter : public Writer { +public: + size_t write(const char* buffer, size_t count) override { + size_t bytesFlushed = 0; + + while (count) { + if (m_bytesUsed == 0 && count >= TBufferSize) { + // special case: write-thru, skip the copy + bytesFlushed += writeInternal(buffer, count); + count = 0; + } else if (m_bytesUsed + count < TBufferSize) { + // Write that will fit in the buffer, just copy to intermediate buffer + memcpy(m_buffer + m_bytesUsed, buffer, count); + m_bytesUsed += count; + count = 0; + } else { + // Need to write partial, then flush buffer + size_t bytesToWrite = TBufferSize - m_bytesUsed; + // Copy this block in to place + memcpy(m_buffer + m_bytesUsed, buffer, bytesToWrite); + m_bytesUsed += bytesToWrite; + + // Flush to underlying + bytesFlushed += flush(); + // Step the read pointer ahead + buffer += bytesToWrite; + // Decrement remaining bytes + count -= bytesToWrite; + } + } + + return bytesFlushed; + } + + // Flush the internal buffer to the underlying interface. + size_t flush() override { + size_t bytesToWrite = m_bytesUsed; + + if (bytesToWrite > 0) { + m_bytesUsed = 0; + return writeInternal(m_buffer, bytesToWrite); + } else { + return 0; + } + } + +protected: + virtual size_t writeInternal(const char* buffer, size_t count) = 0; + +private: + char m_buffer[TBufferSize]; + size_t m_bytesUsed = 0; +}; diff --git a/unit_tests/tests/tests.mk b/unit_tests/tests/tests.mk index a959426bd4..1d41fd6150 100644 --- a/unit_tests/tests/tests.mk +++ b/unit_tests/tests/tests.mk @@ -14,6 +14,7 @@ TESTS_SRC_CPP = \ tests/ignition_injection/test_fuelCut.cpp \ tests/ignition_injection/test_fuel_computer.cpp \ tests/ignition_injection/test_injector_model.cpp \ + tests/util/test_buffered_writer.cpp \ tests/test_util.cpp \ tests/test_hardware_reinit.cpp \ tests/test_ion.cpp \ diff --git a/unit_tests/tests/util/test_buffered_writer.cpp b/unit_tests/tests/util/test_buffered_writer.cpp new file mode 100644 index 0000000000..66928a9ef6 --- /dev/null +++ b/unit_tests/tests/util/test_buffered_writer.cpp @@ -0,0 +1,85 @@ +#include "buffered_writer.h" +#include +#include + +using ::testing::_; +using ::testing::Return; +using ::testing::StrictMock; + +template +struct MockBufferedWriter : public BufferedWriter +{ + MOCK_METHOD(size_t, writeInternal, (const char*, size_t), (override)); +}; + +static const char* testBuffer = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +TEST(BufferedWriter, WriteSmall) { + // No calls to dut expected + StrictMock> dut; + + EXPECT_EQ(0, dut.write(testBuffer, 5)); +} + +TEST(BufferedWriter, WriteSmallFlush) { + StrictMock> dut; + EXPECT_CALL(dut, writeInternal(_, 5)).WillOnce(Return(5)); + + ASSERT_EQ(0, dut.write(testBuffer, 5)); + + EXPECT_EQ(dut.flush(), 5); +} + +TEST(BufferedWriter, WriteMultipleSmall) { + StrictMock> dut; + { + EXPECT_CALL(dut, writeInternal(_, 10)).WillOnce(Return(10)); + EXPECT_CALL(dut, writeInternal(_, 2)).WillOnce(Return(2)); + } + + EXPECT_EQ(0, dut.write(testBuffer, 3)); + EXPECT_EQ(0, dut.write(testBuffer, 3)); + EXPECT_EQ(0, dut.write(testBuffer, 3)); + EXPECT_EQ(10, dut.write(testBuffer, 3)); // <- this one should trigger a flush + + // Flush the remainder + EXPECT_EQ(dut.flush(), 2); +} + +TEST(BufferedWriter, WriteSingleFullBufferSize) { + StrictMock> dut; + + EXPECT_CALL(dut, writeInternal(_, 50)).WillOnce(Return(50)); + + EXPECT_EQ(50, dut.write(testBuffer, 50)); + + // Nothing left to flush! + EXPECT_EQ(0, dut.flush()); +} + +TEST(BufferedWriter, WriteLarge) { + StrictMock> dut; + + { + EXPECT_CALL(dut, writeInternal(_, 45)).WillOnce(Return(45)); + } + + EXPECT_EQ(45, dut.write(testBuffer, 45)); + + EXPECT_EQ(0, dut.flush()); +} + +TEST(BufferedWriter, WriteLargeAfterSmall) { + StrictMock> dut; + + { + EXPECT_CALL(dut, writeInternal(_, 10)).WillOnce(Return(10)); + EXPECT_CALL(dut, writeInternal(_, 36)).WillOnce(Return(36)); + } + + EXPECT_EQ(0, dut.write(testBuffer, 1)); + + EXPECT_EQ(46, dut.write(testBuffer, 45)); + + EXPECT_EQ(0, dut.flush()); +}