diff options
Diffstat (limited to 'pw_kvs/flash_partition_stream_test.cc')
-rw-r--r-- | pw_kvs/flash_partition_stream_test.cc | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/pw_kvs/flash_partition_stream_test.cc b/pw_kvs/flash_partition_stream_test.cc new file mode 100644 index 000000000..47fd974db --- /dev/null +++ b/pw_kvs/flash_partition_stream_test.cc @@ -0,0 +1,396 @@ +// Copyright 2022 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#include <algorithm> +#include <array> +#include <cstddef> +#include <cstring> +#include <span> + +#include "gtest/gtest.h" +#include "public/pw_kvs/flash_memory.h" +#include "pw_kvs/fake_flash_memory.h" +#include "pw_kvs/flash_memory.h" +#include "pw_kvs_private/config.h" +#include "pw_log/log.h" +#include "pw_random/xor_shift.h" + +#if PW_CXX_STANDARD_IS_SUPPORTED(17) + +#ifndef PW_FLASH_TEST_ALIGNMENT +#define PW_FLASH_TEST_ALIGNMENT 1 +#endif + +namespace pw::kvs { +namespace { + +class FlashStreamTest : public ::testing::Test { + protected: + FlashStreamTest() : flash_(kFlashAlignment), partition_(&flash_) {} + + void InitBufferToFill(ByteSpan buffer_span, char fill) { + std::memset(buffer_span.data(), fill, buffer_span.size_bytes()); + } + + void InitBufferToRandom(ByteSpan buffer_span, uint64_t seed) { + random::XorShiftStarRng64 rng(seed); + + std::memset(buffer_span.data(), + static_cast<int>(flash_.erased_memory_content()), + buffer_span.size()); + ASSERT_EQ(OkStatus(), rng.Get(buffer_span).status()); + } + + void VerifyFlash(ConstByteSpan verify_bytes, size_t offset = 0) { + // Should be defined as same size. + EXPECT_EQ(source_buffer_.size(), flash_.buffer().size_bytes()); + + // Can't allow it to march off the end of source_buffer_. + ASSERT_LE((verify_bytes.size_bytes() + offset), source_buffer_.size()); + + for (size_t i = 0; i < verify_bytes.size_bytes(); i++) { + ASSERT_EQ(source_buffer_[i + offset], verify_bytes[i]); + } + } + + void VerifyFlashContent(ConstByteSpan verify_bytes, size_t offset = 0) { + // Can't allow it to march off the end of source_buffer_. + ASSERT_LE((verify_bytes.size_bytes() + offset), + flash_.buffer().size_bytes()); + + for (size_t i = 0; i < verify_bytes.size_bytes(); i++) { + ASSERT_EQ(flash_.buffer()[i + offset], verify_bytes[i]); + } + } + + void DoWriteInChunks(size_t chunk_write_size_bytes, uint64_t seed) { + InitBufferToRandom(std::span(source_buffer_), seed); + ConstByteSpan write_data = std::span(source_buffer_); + + ASSERT_EQ(OkStatus(), partition_.Erase()); + + FlashPartition::Writer writer(partition_); + + while (write_data.size_bytes() > 0) { + size_t offset_before_write = writer.Tell(); + size_t write_chunk_size = + std::min(chunk_write_size_bytes, write_data.size_bytes()); + + ConstByteSpan write_chunk = write_data.first(write_chunk_size); + ASSERT_EQ(OkStatus(), writer.Write(write_chunk)); + VerifyFlashContent(write_chunk, offset_before_write); + + write_data = write_data.subspan(write_chunk_size); + + ASSERT_EQ(writer.ConservativeWriteLimit(), write_data.size_bytes()); + } + + VerifyFlashContent(std::span(source_buffer_)); + } + + void DoReadInChunks(size_t chunk_read_size_bytes, + uint64_t seed, + size_t start_offset, + size_t bytes_to_read) { + InitBufferToRandom(flash_.buffer(), seed); + + ASSERT_LE((start_offset + bytes_to_read), flash_.buffer().size_bytes()); + + FlashPartition::Reader reader(partition_); + ASSERT_EQ(reader.ConservativeReadLimit(), flash_.buffer().size_bytes()); + + ASSERT_EQ(reader.Seek(start_offset), OkStatus()); + ASSERT_EQ(reader.ConservativeReadLimit(), + flash_.buffer().size_bytes() - start_offset); + + while (bytes_to_read > 0) { + ASSERT_EQ(start_offset, reader.Tell()); + + size_t chunk_size = std::min(chunk_read_size_bytes, bytes_to_read); + + ByteSpan read_chunk = std::span(source_buffer_).first(chunk_size); + InitBufferToFill(read_chunk, 0); + ASSERT_EQ(read_chunk.size_bytes(), chunk_size); + + auto result = reader.Read(read_chunk); + ASSERT_EQ(result.status(), OkStatus()); + ASSERT_EQ(result.value().size_bytes(), chunk_size); + VerifyFlashContent(read_chunk, start_offset); + + start_offset += chunk_size; + bytes_to_read -= chunk_size; + + ASSERT_EQ(reader.ConservativeReadLimit(), + flash_.buffer().size_bytes() - start_offset); + } + } + + static constexpr size_t kFlashAlignment = PW_FLASH_TEST_ALIGNMENT; + static constexpr size_t kSectorSize = 2048; + static constexpr size_t kSectorCount = 2; + static constexpr size_t kFPDataSize = (kSectorCount * kSectorSize); + + FakeFlashMemoryBuffer<kSectorSize, kSectorCount> flash_; + FlashPartition partition_; + std::array<std::byte, kFPDataSize> source_buffer_; + size_t size_bytes_; +}; + +TEST_F(FlashStreamTest, Write_1_Byte_Chunks) { + // Write in 1 byte chunks. + DoWriteInChunks(1, 0xab1234); +} + +TEST_F(FlashStreamTest, Write_5_Byte_Chunks) { + // Write in 5 byte chunks. + DoWriteInChunks(5, 0xdc2274); +} + +TEST_F(FlashStreamTest, Write_16_Byte_Chunks) { + // Write in 16 byte chunks. + DoWriteInChunks(16, 0xef8224); +} + +TEST_F(FlashStreamTest, Write_255_Byte_Chunks) { + // Write in 255 byte chunks. + DoWriteInChunks(255, 0xffe1348); +} + +TEST_F(FlashStreamTest, Write_256_Byte_Chunks) { + // Write in 256 byte chunks. + DoWriteInChunks(256, 0xe11234); +} + +TEST_F(FlashStreamTest, Read_1_Byte_Chunks) { + // Read in 1 byte chunks. + DoReadInChunks(1, 0x7643ff, 0, flash_.buffer().size_bytes()); +} + +TEST_F(FlashStreamTest, Read_16_Byte_Chunks) { + // Read in 16 byte chunks. + DoReadInChunks(16, 0x61e234, 0, flash_.buffer().size_bytes()); +} + +TEST_F(FlashStreamTest, Read_255_Byte_Chunks) { + // Read in 256 byte chunks. + DoReadInChunks(255, 0xe13514, 0, flash_.buffer().size_bytes()); +} + +TEST_F(FlashStreamTest, Read_256_Byte_Chunks) { + // Read in 256 byte chunks. + DoReadInChunks(256, 0xe11234, 0, flash_.buffer().size_bytes()); +} + +TEST_F(FlashStreamTest, Read_256_Byte_Chunks_With_Offset) { + // Read in 256 byte chunks. + DoReadInChunks(256, 0xfffe34, 1024, (flash_.buffer().size_bytes() - 1024)); +} + +TEST_F(FlashStreamTest, Read_Multiple_Seeks) { + static const size_t kSeekReadSizeBytes = 512; + static const size_t kSeekReadIterations = 4; + ASSERT_GE(flash_.buffer().size_bytes(), + (kSeekReadIterations * (2 * kSeekReadSizeBytes))); + + InitBufferToRandom(flash_.buffer(), 0xffde176); + FlashPartition::Reader reader(partition_); + + for (size_t i = 0; i < kSeekReadIterations; i++) { + size_t start_offset = kSeekReadSizeBytes + (i * 2 * kSeekReadSizeBytes); + ASSERT_EQ(reader.Seek(start_offset), OkStatus()); + ASSERT_EQ(start_offset, reader.Tell()); + + ByteSpan read_chunk = std::span(source_buffer_).first(kSeekReadSizeBytes); + InitBufferToFill(read_chunk, 0); + + auto result = reader.Read(read_chunk); + ASSERT_EQ(result.status(), OkStatus()); + ASSERT_EQ(result.value().size_bytes(), kSeekReadSizeBytes); + VerifyFlashContent(read_chunk, start_offset); + ASSERT_EQ(start_offset + kSeekReadSizeBytes, reader.Tell()); + } +} + +TEST_F(FlashStreamTest, Read_Seek_Forward_and_Back) { + static const size_t kSeekReadSizeBytes = 256; + static const size_t kTotalIterations = 3; + static const size_t kSeekReadIterations = + flash_.buffer().size_bytes() / (2 * kSeekReadSizeBytes); + + InitBufferToRandom(flash_.buffer(), 0xffde176); + FlashPartition::Reader reader(partition_); + + for (size_t outer_count = 0; outer_count < kTotalIterations; outer_count++) { + // Seek and read going forward. + for (size_t i = 0; i < kSeekReadIterations; i++) { + size_t start_offset = kSeekReadSizeBytes + (i * 2 * kSeekReadSizeBytes); + ASSERT_EQ(reader.Seek(start_offset), OkStatus()); + ASSERT_EQ(start_offset, reader.Tell()); + + ByteSpan read_chunk = std::span(source_buffer_).first(kSeekReadSizeBytes); + InitBufferToFill(read_chunk, 0); + + auto result = reader.Read(read_chunk); + ASSERT_EQ(result.status(), OkStatus()); + ASSERT_EQ(result.value().size_bytes(), kSeekReadSizeBytes); + VerifyFlashContent(read_chunk, start_offset); + ASSERT_EQ(start_offset + kSeekReadSizeBytes, reader.Tell()); + } + + // Seek and read going backward. + for (size_t j = (kSeekReadIterations * 2); j > 0; j--) { + size_t start_offset = (j - 1) * kSeekReadSizeBytes; + ASSERT_EQ(reader.Seek(start_offset), OkStatus()); + ASSERT_EQ(start_offset, reader.Tell()); + ASSERT_GE(reader.ConservativeReadLimit(), kSeekReadSizeBytes); + + ByteSpan read_chunk = std::span(source_buffer_).first(kSeekReadSizeBytes); + InitBufferToFill(read_chunk, 0); + + auto result = reader.Read(read_chunk); + ASSERT_EQ(result.status(), OkStatus()); + ASSERT_EQ(result.value().size_bytes(), kSeekReadSizeBytes); + VerifyFlashContent(read_chunk, start_offset); + ASSERT_EQ(start_offset + kSeekReadSizeBytes, reader.Tell()); + } + } +} + +TEST_F(FlashStreamTest, Read_Past_End) { + InitBufferToRandom(flash_.buffer(), 0xcccde176); + FlashPartition::Reader reader(partition_); + + static const size_t kBytesForFinalRead = 50; + + ByteSpan read_chunk = std::span(source_buffer_) + .first(source_buffer_.size() - kBytesForFinalRead); + + auto result = reader.Read(read_chunk); + ASSERT_EQ(result.status(), OkStatus()); + ASSERT_EQ(result.value().size_bytes(), read_chunk.size_bytes()); + ASSERT_EQ(reader.Tell(), read_chunk.size_bytes()); + ASSERT_EQ(reader.ConservativeReadLimit(), kBytesForFinalRead); + ASSERT_EQ(result.value().data(), read_chunk.data()); + VerifyFlashContent(read_chunk); + + result = reader.Read(read_chunk); + ASSERT_EQ(result.status(), OkStatus()); + ASSERT_EQ(result.value().size_bytes(), kBytesForFinalRead); + ASSERT_EQ(reader.Tell(), flash_.buffer().size_bytes()); + ASSERT_EQ(reader.ConservativeReadLimit(), 0U); + ASSERT_EQ(result.value().data(), read_chunk.data()); + VerifyFlashContent(result.value(), read_chunk.size_bytes()); +} + +TEST_F(FlashStreamTest, Read_Past_End_After_Seek) { + InitBufferToRandom(flash_.buffer(), 0xddcde176); + FlashPartition::Reader reader(partition_); + + static const size_t kBytesForFinalRead = 50; + size_t start_offset = flash_.buffer().size_bytes() - kBytesForFinalRead; + ASSERT_EQ(reader.Seek(start_offset), OkStatus()); + + ASSERT_EQ(start_offset, reader.Tell()); + ASSERT_EQ(reader.ConservativeReadLimit(), kBytesForFinalRead); + + ByteSpan read_chunk = std::span(source_buffer_); + auto result = reader.Read(read_chunk); + ASSERT_EQ(result.status(), OkStatus()); + ASSERT_EQ(result.value().size_bytes(), kBytesForFinalRead); + ASSERT_EQ(reader.Tell(), flash_.buffer().size_bytes()); + ASSERT_EQ(reader.ConservativeReadLimit(), 0U); + ASSERT_EQ(result.value().data(), read_chunk.data()); + VerifyFlashContent(result.value(), start_offset); +} + +TEST_F(FlashStreamTest, Read_Out_Of_Range) { + InitBufferToRandom(flash_.buffer(), 0x531de176); + FlashPartition::Reader reader(partition_); + + ByteSpan read_chunk = std::span(source_buffer_); + + auto result = reader.Read(read_chunk); + ASSERT_EQ(result.status(), OkStatus()); + ASSERT_EQ(result.value().size_bytes(), read_chunk.size_bytes()); + ASSERT_EQ(reader.Tell(), read_chunk.size_bytes()); + ASSERT_EQ(reader.ConservativeReadLimit(), 0U); + ASSERT_EQ(result.value().data(), read_chunk.data()); + VerifyFlashContent(read_chunk); + + result = reader.Read(read_chunk); + ASSERT_EQ(result.status(), Status::OutOfRange()); + ASSERT_EQ(reader.Tell(), flash_.buffer().size_bytes()); + ASSERT_EQ(reader.ConservativeReadLimit(), 0U); +} + +TEST_F(FlashStreamTest, Read_Out_Of_Range_After_Seek) { + InitBufferToRandom(flash_.buffer(), 0x8c94566); + FlashPartition::Reader reader(partition_); + + ByteSpan read_chunk = std::span(source_buffer_); + + ASSERT_EQ(reader.Seek(flash_.buffer().size_bytes()), OkStatus()); + ASSERT_EQ(reader.Tell(), flash_.buffer().size_bytes()); + ASSERT_EQ(reader.ConservativeReadLimit(), 0U); + + auto result = reader.Read(read_chunk); + ASSERT_EQ(result.status(), Status::OutOfRange()); + ASSERT_EQ(reader.Tell(), flash_.buffer().size_bytes()); + ASSERT_EQ(reader.ConservativeReadLimit(), 0U); +} + +TEST_F(FlashStreamTest, Reader_Seek_Ops) { + size_t kPartitionSizeBytes = flash_.buffer().size_bytes(); + FlashPartition::Reader reader(partition_); + + // Seek from 0 to past end. + ASSERT_EQ(reader.Seek(kPartitionSizeBytes + 5), Status::OutOfRange()); + ASSERT_EQ(reader.Tell(), 0U); + + // Seek to end then seek again going past end. + ASSERT_EQ(reader.Seek(0), OkStatus()); + ASSERT_EQ(reader.Tell(), 0U); + ASSERT_EQ(reader.ConservativeReadLimit(), kPartitionSizeBytes); + + ASSERT_EQ(reader.Seek(kPartitionSizeBytes, + FlashPartition::Reader::Whence::kCurrent), + OkStatus()); + ASSERT_EQ(reader.Tell(), kPartitionSizeBytes); + ASSERT_EQ(reader.ConservativeReadLimit(), 0U); + + ASSERT_EQ(reader.Seek(5, FlashPartition::Reader::Whence::kCurrent), + Status::OutOfRange()); + ASSERT_EQ(reader.Tell(), kPartitionSizeBytes); + ASSERT_EQ(reader.ConservativeReadLimit(), 0U); + + // Seek to beginning then seek backwards going past start. + ASSERT_EQ(reader.Seek(0), OkStatus()); + ASSERT_EQ(reader.Seek(-5, FlashPartition::Reader::Whence::kCurrent), + Status::OutOfRange()); + ASSERT_EQ(reader.Tell(), 0U); + ASSERT_EQ(reader.ConservativeReadLimit(), kPartitionSizeBytes); +} + +TEST_F(FlashStreamTest, Invald_Ops) { + FlashPartition::Reader reader(partition_); + ASSERT_EQ(reader.ConservativeWriteLimit(), 0U); + + FlashPartition::Writer writer(partition_); + ASSERT_EQ(writer.ConservativeReadLimit(), 0U); +} + +} // namespace +} // namespace pw::kvs + +#endif // PW_CXX_STANDARD_IS_SUPPORTED(17) |