| // 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 "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" |
| #include "pw_span/span.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(span(source_buffer_), seed); |
| ConstByteSpan write_data = 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(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 = 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 = 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 = 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 = 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 = |
| 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 = 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 = 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 = 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) |