blob: e0cccee2102096dacbd84c7d5e9ac8b00e3e5092 [file] [log] [blame]
// 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)