blob: acaf2ff229f6df049e10d707965e85fb52467fbe [file] [log] [blame]
// Copyright 2020 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 "pw_blob_store/blob_store.h"
#include <array>
#include <cstddef>
#include <cstring>
#include "gtest/gtest.h"
#include "pw_kvs/crc16_checksum.h"
#include "pw_kvs/fake_flash_memory.h"
#include "pw_kvs/flash_memory.h"
#include "pw_kvs/test_key_value_store.h"
#include "pw_log/log.h"
#include "pw_random/xor_shift.h"
#include "pw_span/span.h"
#ifndef PW_FLASH_TEST_ALIGNMENT
#define PW_FLASH_TEST_ALIGNMENT 1
#endif
namespace pw::blob_store {
namespace {
class BlobStoreTest : public ::testing::Test {
protected:
static constexpr char kBlobTitle[] = "TestBlobBlock";
BlobStoreTest() : flash_(kFlashAlignment), partition_(&flash_) {}
void InitFlashTo(span<const std::byte> contents) {
ASSERT_EQ(OkStatus(), partition_.Erase());
std::memcpy(flash_.buffer().data(), contents.data(), contents.size());
}
void InitSourceBufferToRandom(uint64_t seed,
size_t init_size_bytes = kBlobDataSize) {
ASSERT_LE(init_size_bytes, source_buffer_.size());
random::XorShiftStarRng64 rng(seed);
std::memset(source_buffer_.data(),
static_cast<int>(flash_.erased_memory_content()),
source_buffer_.size());
ASSERT_EQ(OkStatus(),
rng.Get(span(source_buffer_).first(init_size_bytes)).status());
}
void InitSourceBufferToFill(char fill,
size_t fill_size_bytes = kBlobDataSize) {
ASSERT_LE(fill_size_bytes, source_buffer_.size());
std::memset(source_buffer_.data(),
static_cast<int>(flash_.erased_memory_content()),
source_buffer_.size());
std::memset(source_buffer_.data(), fill, fill_size_bytes);
}
// Fill the source buffer with random pattern based on given seed, written to
// BlobStore in specified chunk size.
void WriteTestBlock(size_t write_size_bytes = kBlobDataSize) {
ASSERT_LE(write_size_bytes, source_buffer_.size());
constexpr size_t kBufferSize = 256;
kvs::ChecksumCrc16 checksum;
ConstByteSpan write_data = span(source_buffer_).first(write_size_bytes);
BlobStoreBuffer<kBufferSize> blob(
kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobWriterWithBuffer writer(blob);
EXPECT_EQ(OkStatus(), writer.Open());
ASSERT_EQ(OkStatus(), writer.Write(write_data));
EXPECT_EQ(OkStatus(), writer.Close());
// Use reader to check for valid data.
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
Result<ConstByteSpan> result = reader.GetMemoryMappedBlob();
ASSERT_TRUE(result.ok());
EXPECT_EQ(write_size_bytes, result.value().size_bytes());
VerifyFlash(result.value().first(write_size_bytes));
VerifyFlash(flash_.buffer().first(write_size_bytes));
EXPECT_EQ(OkStatus(), reader.Close());
}
// Open a new blob instance and read the blob using the given read chunk size.
void ChunkReadTest(size_t read_chunk_size) {
kvs::ChecksumCrc16 checksum;
VerifyFlash(flash_.buffer());
constexpr size_t kBufferSize = 16;
BlobStoreBuffer<kBufferSize> blob(
kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
// Use reader to check for valid data.
BlobStore::BlobReader reader1(blob);
ASSERT_EQ(OkStatus(), reader1.Open());
Result<ConstByteSpan> possible_blob = reader1.GetMemoryMappedBlob();
ASSERT_TRUE(possible_blob.ok());
VerifyFlash(possible_blob.value());
EXPECT_EQ(OkStatus(), reader1.Close());
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
std::array<std::byte, kBlobDataSize> read_buffer;
ByteSpan read_span = read_buffer;
while (read_span.size_bytes() > 0) {
size_t read_size = std::min(read_span.size_bytes(), read_chunk_size);
PW_LOG_DEBUG("Do write of %u bytes, %u bytes remain",
static_cast<unsigned>(read_size),
static_cast<unsigned>(read_span.size_bytes()));
ASSERT_EQ(read_span.size_bytes(), reader.ConservativeReadLimit());
auto result = reader.Read(read_span.first(read_size));
ASSERT_EQ(result.status(), OkStatus());
read_span = read_span.subspan(read_size);
}
EXPECT_EQ(OkStatus(), reader.Close());
VerifyFlash(read_buffer);
}
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]);
}
}
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 kBlobDataSize = (kSectorCount * kSectorSize);
kvs::FakeFlashMemoryBuffer<kSectorSize, kSectorCount> flash_;
kvs::FlashPartition partition_;
std::array<std::byte, kBlobDataSize> source_buffer_;
};
TEST_F(BlobStoreTest, Init_Ok) {
// TODO: Do init test with flash/kvs explicitly in the different possible
// entry states.
constexpr size_t kBufferSize = 256;
BlobStoreBuffer<kBufferSize> blob(
"Blob_OK", partition_, nullptr, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
}
TEST_F(BlobStoreTest, Writer_ConservativeLimits) {
constexpr size_t kBufferSize = 256;
BlobStoreBuffer<kBufferSize> blob(
"Blob_OK", partition_, nullptr, kvs::TestKvs(), kBufferSize);
ASSERT_EQ(OkStatus(), blob.Init());
BlobStore::BlobWriterWithBuffer writer(blob);
ASSERT_EQ(OkStatus(), writer.Open());
EXPECT_EQ(writer.ConservativeReadLimit(), 0u);
EXPECT_EQ(writer.ConservativeWriteLimit(), kSectorSize * kSectorCount);
ASSERT_EQ(OkStatus(), writer.Close());
BlobStore::DeferredWriterWithBuffer deferred_writer(blob);
ASSERT_EQ(OkStatus(), deferred_writer.Open());
EXPECT_EQ(deferred_writer.ConservativeReadLimit(), 0u);
EXPECT_EQ(deferred_writer.ConservativeWriteLimit(), kBufferSize);
}
// Write to the blob using a flash_write_size_bytes smaller than the
// buffer size. Use Write operations smaller than flash_write_size_bytes
// to ensure it checks the internal buffering path.
TEST_F(BlobStoreTest, OversizedWriteBuffer) {
size_t write_size_bytes = 8;
ASSERT_LE(write_size_bytes, source_buffer_.size());
constexpr size_t kBufferSize = 256;
kvs::ChecksumCrc16 checksum;
InitSourceBufferToRandom(0x123d123);
ConstByteSpan write_data = span(source_buffer_);
ConstByteSpan original_source = span(source_buffer_);
EXPECT_EQ(OkStatus(), partition_.Erase());
BlobStoreBuffer<kBufferSize> blob(
kBlobTitle, partition_, &checksum, kvs::TestKvs(), 64);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobWriterWithBuffer writer(blob);
EXPECT_EQ(OkStatus(), writer.Open());
while (write_data.size_bytes() > 0) {
ASSERT_EQ(OkStatus(), writer.Write(write_data.first(write_size_bytes)));
write_data = write_data.subspan(write_size_bytes);
}
EXPECT_EQ(OkStatus(), writer.Close());
// Use reader to check for valid data.
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
Result<ConstByteSpan> result = reader.GetMemoryMappedBlob();
ASSERT_TRUE(result.ok());
EXPECT_EQ(original_source.size_bytes(), result.value().size_bytes());
VerifyFlash(result.value());
VerifyFlash(flash_.buffer());
EXPECT_EQ(OkStatus(), reader.Close());
}
TEST_F(BlobStoreTest, Reader_ConservativeLimits) {
InitSourceBufferToRandom(0x11309);
WriteTestBlock();
kvs::ChecksumCrc16 checksum;
constexpr size_t kBufferSize = 16;
BlobStoreBuffer<kBufferSize> blob(
"TestBlobBlock", partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
EXPECT_TRUE(blob.HasData());
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
EXPECT_EQ(kBlobDataSize, reader.ConservativeReadLimit());
EXPECT_EQ(0u, reader.ConservativeWriteLimit());
}
TEST_F(BlobStoreTest, IsOpen) {
constexpr size_t kBufferSize = 256;
BlobStoreBuffer<kBufferSize> blob(
"Blob_open", partition_, nullptr, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::DeferredWriterWithBuffer deferred_writer(blob);
EXPECT_EQ(false, deferred_writer.IsOpen());
EXPECT_EQ(OkStatus(), deferred_writer.Open());
EXPECT_EQ(true, deferred_writer.IsOpen());
EXPECT_EQ(OkStatus(), deferred_writer.Close());
EXPECT_EQ(false, deferred_writer.IsOpen());
BlobStore::BlobWriterWithBuffer writer(blob);
EXPECT_EQ(false, writer.IsOpen());
EXPECT_EQ(OkStatus(), writer.Open());
EXPECT_EQ(true, writer.IsOpen());
EXPECT_FALSE(blob.HasData());
// Need to write something, so the blob reader is able to open.
std::array<std::byte, 64> tmp_buffer = {};
EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
EXPECT_EQ(OkStatus(), writer.Close());
EXPECT_EQ(false, writer.IsOpen());
EXPECT_TRUE(blob.HasData());
BlobStore::BlobReader reader(blob);
EXPECT_EQ(false, reader.IsOpen());
ASSERT_EQ(OkStatus(), reader.Open());
EXPECT_EQ(true, reader.IsOpen());
EXPECT_EQ(OkStatus(), reader.Close());
EXPECT_EQ(false, reader.IsOpen());
}
// Write to the blob using no write buffer size. Write operations must be
// multiples of flash_write_size_bytes.
TEST_F(BlobStoreTest, NoWriteBuffer_1Alignment) {
if (kFlashAlignment > 1) {
// Test not valid for flash alignments greater than 1.
return;
}
const size_t kWriteSizeBytes = 1;
kvs::ChecksumCrc16 checksum;
InitSourceBufferToRandom(0xaabd123);
ConstByteSpan write_data = span(source_buffer_);
ConstByteSpan original_source = span(source_buffer_);
EXPECT_EQ(OkStatus(), partition_.Erase());
BlobStore blob(kBlobTitle,
partition_,
&checksum,
kvs::TestKvs(),
span<std::byte>(),
kWriteSizeBytes);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobWriterWithBuffer writer(blob);
EXPECT_EQ(OkStatus(), writer.Open());
size_t test_write_size[] = {1, 1, 2, 4, 32, 128};
for (size_t size : test_write_size) {
ASSERT_EQ(OkStatus(), writer.Write(write_data.first(size)));
write_data = write_data.subspan(size);
}
while (write_data.size_bytes() > 0) {
const size_t finish_write_size = 8;
ASSERT_EQ(OkStatus(), writer.Write(write_data.first(finish_write_size)));
write_data = write_data.subspan(finish_write_size);
}
EXPECT_EQ(write_data.size_bytes(), 0U);
EXPECT_EQ(OkStatus(), writer.Close());
// Use reader to check for valid data.
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
Result<ConstByteSpan> result = reader.GetMemoryMappedBlob();
ASSERT_TRUE(result.ok());
EXPECT_EQ(original_source.size_bytes(), result.value().size_bytes());
VerifyFlash(result.value());
VerifyFlash(flash_.buffer());
EXPECT_EQ(OkStatus(), reader.Close());
}
// Write to the blob using no write buffer size. Write operations must be
// multiples of flash_write_size_bytes.
TEST_F(BlobStoreTest, NoWriteBuffer_16Alignment) {
if (kFlashAlignment > 16) {
// Test not valid for flash alignments greater than 16.
return;
}
const size_t kWriteSizeBytes = 16;
kvs::ChecksumCrc16 checksum;
InitSourceBufferToRandom(0x6745d123);
ConstByteSpan write_data = span(source_buffer_);
ConstByteSpan original_source = span(source_buffer_);
EXPECT_EQ(OkStatus(), partition_.Erase());
BlobStore blob(kBlobTitle,
partition_,
&checksum,
kvs::TestKvs(),
span<std::byte>(),
kWriteSizeBytes);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobWriterWithBuffer writer(blob);
EXPECT_EQ(OkStatus(), writer.Open());
ASSERT_EQ(Status::InvalidArgument(), writer.Write(write_data.first(1)));
ASSERT_EQ(Status::InvalidArgument(),
writer.Write(write_data.first(kWriteSizeBytes / 2)));
ASSERT_EQ(OkStatus(), writer.Write(write_data.first(4 * kWriteSizeBytes)));
write_data = write_data.subspan(4 * kWriteSizeBytes);
ASSERT_EQ(Status::InvalidArgument(), writer.Write(write_data.first(1)));
ASSERT_EQ(Status::InvalidArgument(),
writer.Write(write_data.first(kWriteSizeBytes / 2)));
while (write_data.size_bytes() > 0) {
ASSERT_EQ(OkStatus(), writer.Write(write_data.first(kWriteSizeBytes)));
write_data = write_data.subspan(kWriteSizeBytes);
}
EXPECT_EQ(OkStatus(), writer.Close());
// Use reader to check for valid data.
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
Result<ConstByteSpan> result = reader.GetMemoryMappedBlob();
ASSERT_TRUE(result.ok());
EXPECT_EQ(original_source.size_bytes(), result.value().size_bytes());
VerifyFlash(result.value());
VerifyFlash(flash_.buffer());
EXPECT_EQ(OkStatus(), reader.Close());
}
TEST_F(BlobStoreTest, FileName) {
InitSourceBufferToRandom(0x8675309);
WriteTestBlock();
constexpr std::string_view kFileName("my_file_1.bin");
std::array<std::byte, 64> tmp_buffer = {};
static_assert(kFileName.size() <= tmp_buffer.size());
kvs::ChecksumCrc16 checksum;
constexpr size_t kBufferSize = 256;
{
// Create/init a blob store in a nested scope so it can be re-initialized
// later when checking the read.
BlobStoreBuffer<kBufferSize> blob(
kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobWriterWithBuffer<kFileName.size()> writer(blob);
EXPECT_EQ(OkStatus(), writer.Open());
EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
EXPECT_EQ(OkStatus(), writer.Close());
EXPECT_EQ(OkStatus(),
kvs::TestKvs().acquire()->Get(kBlobTitle, tmp_buffer).status());
}
BlobStoreBuffer<kBufferSize> blob(
kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
// Ensure the file name can be read from a reader.
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
memset(tmp_buffer.data(), 0, tmp_buffer.size());
StatusWithSize sws = reader.GetFileName(
{reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
EXPECT_EQ(OkStatus(), sws.status());
ASSERT_EQ(kFileName.size(), sws.size());
EXPECT_EQ(memcmp(kFileName.data(), tmp_buffer.data(), kFileName.size()), 0);
}
TEST_F(BlobStoreTest, FileNameUndersizedRead) {
InitSourceBufferToRandom(0x8675309);
WriteTestBlock();
constexpr std::string_view kFileName("my_file_1.bin");
std::array<std::byte, 4> tmp_buffer = {};
static_assert(kFileName.size() > tmp_buffer.size());
kvs::ChecksumCrc16 checksum;
constexpr size_t kBufferSize = 256;
BlobStoreBuffer<kBufferSize> blob(
kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobWriterWithBuffer<kFileName.size()> writer(blob);
EXPECT_EQ(OkStatus(), writer.Open());
EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
EXPECT_EQ(OkStatus(), writer.Write(as_bytes(span("some interesting data"))));
EXPECT_EQ(OkStatus(), writer.Close());
// Ensure the file name can be read from a reader.
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
StatusWithSize sws = reader.GetFileName(
{reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
EXPECT_EQ(Status::ResourceExhausted(), sws.status());
ASSERT_EQ(tmp_buffer.size(), sws.size());
EXPECT_EQ(memcmp(kFileName.data(), tmp_buffer.data(), sws.size()), 0);
}
TEST_F(BlobStoreTest, FileNameUndersizedSet) {
InitSourceBufferToRandom(0x8675309);
WriteTestBlock();
constexpr std::string_view kFileName("my_file_1.bin");
kvs::ChecksumCrc16 checksum;
constexpr size_t kBufferSize = 256;
BlobStoreBuffer<kBufferSize> blob(
kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobWriterWithBuffer<2> writer(blob);
EXPECT_EQ(OkStatus(), writer.Open());
EXPECT_EQ(Status::ResourceExhausted(), writer.SetFileName(kFileName));
EXPECT_EQ(OkStatus(), writer.Close());
}
TEST_F(BlobStoreTest, FileNameInvalidation) {
InitSourceBufferToRandom(0x8675309);
WriteTestBlock();
constexpr std::string_view kFileName("sliced_cheese.png");
std::array<std::byte, 64> tmp_buffer = {};
static_assert(kFileName.size() <= tmp_buffer.size());
kvs::ChecksumCrc16 checksum;
constexpr size_t kBufferSize = 256;
BlobStoreBuffer<kBufferSize> blob(
kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobWriterWithBuffer<kFileName.size()> writer(blob);
EXPECT_EQ(OkStatus(), writer.Open());
EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
EXPECT_EQ(OkStatus(), writer.Discard());
EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
EXPECT_EQ(OkStatus(), writer.Close());
// Check that the file name was discarded by Discard().
memset(tmp_buffer.data(), 0, tmp_buffer.size());
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
StatusWithSize sws = reader.GetFileName(
{reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
EXPECT_EQ(Status::NotFound(), sws.status());
ASSERT_EQ(0u, sws.size());
}
TEST_F(BlobStoreTest, NoFileName) {
InitSourceBufferToRandom(0x8675309);
WriteTestBlock();
std::array<std::byte, 64> tmp_buffer = {};
kvs::ChecksumCrc16 checksum;
constexpr size_t kBufferSize = 256;
BlobStoreBuffer<kBufferSize> blob(
kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
// Ensure blobs with no file names work as expected.
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
StatusWithSize sws = reader.GetFileName(
{reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
EXPECT_EQ(Status::NotFound(), sws.status());
ASSERT_EQ(0u, sws.size());
}
TEST_F(BlobStoreTest, V1MetadataBackwardsCompatible) {
constexpr size_t kWriteSize = 25;
WriteTestBlock(kWriteSize);
kvs::ChecksumCrc16 checksum;
constexpr size_t kBufferSize = 16;
BlobStoreBuffer<kBufferSize> blob(
kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
// Read the written data in the current format.
internal::BlobMetadataHeader current_metadata;
ASSERT_EQ(OkStatus(),
kvs::TestKvs().acquire()->Get(kBlobTitle, &current_metadata));
// Re-save only the V1 metadata contents.
ASSERT_EQ(
OkStatus(),
kvs::TestKvs().acquire()->Put(kBlobTitle, current_metadata.v1_metadata));
// Ensure the BlobStore's contents aren't invalid.
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
ASSERT_EQ(kWriteSize, reader.ConservativeReadLimit());
ASSERT_EQ(current_metadata.v1_metadata.data_size_bytes,
reader.ConservativeReadLimit());
}
TEST_F(BlobStoreTest, Discard) {
InitSourceBufferToRandom(0x8675309);
WriteTestBlock();
constexpr char blob_title[] = "TestBlobBlock";
std::array<std::byte, 64> tmp_buffer = {};
kvs::ChecksumCrc16 checksum;
// TODO: Do this test with flash/kvs in the different entry state
// combinations.
constexpr size_t kBufferSize = 256;
BlobStoreBuffer<kBufferSize> blob(
blob_title, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
EXPECT_TRUE(blob.HasData());
BlobStore::BlobWriterWithBuffer writer(blob);
EXPECT_EQ(OkStatus(), writer.Open());
EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
// Blob should NOT be valid to read, because the write data was only buffered,
// and has not been written to flash yet.
EXPECT_FALSE(blob.HasData());
// The write does an implicit erase so there should be no key for this blob.
EXPECT_EQ(Status::NotFound(),
kvs::TestKvs().acquire()->Get(blob_title, tmp_buffer).status());
EXPECT_EQ(OkStatus(), writer.Close());
EXPECT_TRUE(blob.HasData());
EXPECT_EQ(OkStatus(),
kvs::TestKvs().acquire()->Get(blob_title, tmp_buffer).status());
EXPECT_EQ(OkStatus(), writer.Open());
EXPECT_EQ(OkStatus(), writer.Discard());
EXPECT_EQ(OkStatus(), writer.Close());
EXPECT_FALSE(blob.HasData());
EXPECT_EQ(Status::NotFound(),
kvs::TestKvs().acquire()->Get(blob_title, tmp_buffer).status());
}
TEST_F(BlobStoreTest, MultipleErase) {
constexpr size_t kBufferSize = 256;
BlobStoreBuffer<kBufferSize> blob(
"Blob_OK", partition_, nullptr, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobWriterWithBuffer writer(blob);
EXPECT_EQ(OkStatus(), writer.Open());
EXPECT_EQ(OkStatus(), writer.Erase());
EXPECT_EQ(OkStatus(), writer.Erase());
EXPECT_EQ(OkStatus(), writer.Erase());
}
TEST_F(BlobStoreTest, OffsetRead) {
InitSourceBufferToRandom(0x11309);
WriteTestBlock();
constexpr size_t kOffset = 10;
ASSERT_LT(kOffset, kBlobDataSize);
kvs::ChecksumCrc16 checksum;
char name[16] = "TestBlobBlock";
constexpr size_t kBufferSize = 16;
BlobStoreBuffer<kBufferSize> blob(
name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open(kOffset));
std::array<std::byte, kBlobDataSize - kOffset> read_buffer;
ByteSpan read_span = read_buffer;
ASSERT_EQ(read_span.size_bytes(), reader.ConservativeReadLimit());
auto result = reader.Read(read_span);
ASSERT_EQ(result.status(), OkStatus());
EXPECT_EQ(OkStatus(), reader.Close());
VerifyFlash(read_buffer, kOffset);
}
TEST_F(BlobStoreTest, SeekOffsetRead) {
InitSourceBufferToRandom(0x11309);
WriteTestBlock();
constexpr size_t kOffset = 10;
ASSERT_LT(kOffset, kBlobDataSize);
kvs::ChecksumCrc16 checksum;
char name[16] = "TestBlobBlock";
constexpr size_t kBufferSize = 16;
BlobStoreBuffer<kBufferSize> blob(
name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
ASSERT_EQ(OkStatus(), reader.Seek(kOffset));
std::array<std::byte, kBlobDataSize - kOffset> read_buffer;
ByteSpan read_span = read_buffer;
ASSERT_EQ(read_span.size_bytes(), reader.ConservativeReadLimit());
auto result = reader.Read(read_span);
ASSERT_EQ(result.status(), OkStatus());
EXPECT_EQ(OkStatus(), reader.Close());
VerifyFlash(read_buffer, kOffset);
}
TEST_F(BlobStoreTest, InvalidReadOffset) {
InitSourceBufferToRandom(0x11309);
WriteTestBlock();
constexpr size_t kOffset = kBlobDataSize;
kvs::ChecksumCrc16 checksum;
char name[16] = "TestBlobBlock";
constexpr size_t kBufferSize = 16;
BlobStoreBuffer<kBufferSize> blob(
name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobReader reader(blob);
ASSERT_EQ(Status::InvalidArgument(), reader.Open(kOffset));
}
TEST_F(BlobStoreTest, ReadSeekClosedReader) {
InitSourceBufferToRandom(0x11309);
WriteTestBlock();
kvs::ChecksumCrc16 checksum;
char name[16] = "TestBlobBlock";
constexpr size_t kBufferSize = 16;
BlobStoreBuffer<kBufferSize> blob(
name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
ASSERT_EQ(OkStatus(), reader.Close());
EXPECT_EQ(Status::FailedPrecondition(), reader.Seek(0));
std::byte read_buffer[32];
EXPECT_EQ(Status::FailedPrecondition(), reader.Read(read_buffer).status());
}
TEST_F(BlobStoreTest, InvalidSeekOffset) {
InitSourceBufferToRandom(0x11309);
WriteTestBlock();
constexpr size_t kOffset = kBlobDataSize;
kvs::ChecksumCrc16 checksum;
char name[16] = "TestBlobBlock";
constexpr size_t kBufferSize = 16;
BlobStoreBuffer<kBufferSize> blob(
name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
ASSERT_EQ(Status::OutOfRange(), reader.Seek(kOffset));
}
// Write a block to blob and close with part of a write buffer with unflushed
// data.
TEST_F(BlobStoreTest, WriteBufferWithRemainderInBuffer) {
InitSourceBufferToRandom(0x11309);
kvs::ChecksumCrc16 checksum;
constexpr size_t kBufferSize = 256;
BlobStoreBuffer<kBufferSize> blob(
"TestBlobBlock", partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
const size_t write_size_bytes = kBlobDataSize - 10;
ConstByteSpan write_data = span(source_buffer_).first(write_size_bytes);
BlobStore::BlobWriterWithBuffer writer(blob);
EXPECT_EQ(OkStatus(), writer.Open());
ASSERT_EQ(OkStatus(), writer.Write(write_data));
EXPECT_EQ(OkStatus(), writer.Close());
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
EXPECT_EQ(write_size_bytes, reader.ConservativeReadLimit());
}
// Test reading with a read buffer larger than the available data in the blob.
TEST_F(BlobStoreTest, ReadBufferIsLargerThanData) {
InitSourceBufferToRandom(0x57326);
constexpr size_t kWriteBytes = 64;
WriteTestBlock(kWriteBytes);
kvs::ChecksumCrc16 checksum;
char name[16] = "TestBlobBlock";
constexpr size_t kBufferSize = 16;
BlobStoreBuffer<kBufferSize> blob(
name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
EXPECT_EQ(OkStatus(), blob.Init());
BlobStore::BlobReader reader(blob);
ASSERT_EQ(OkStatus(), reader.Open());
EXPECT_EQ(kWriteBytes, reader.ConservativeReadLimit());
std::array<std::byte, kWriteBytes + 10> read_buffer;
ByteSpan read_span = read_buffer;
auto result = reader.Read(read_span);
ASSERT_EQ(result.status(), OkStatus());
EXPECT_EQ(OkStatus(), reader.Close());
}
TEST_F(BlobStoreTest, ChunkRead1) {
InitSourceBufferToRandom(0x8675309);
WriteTestBlock();
ChunkReadTest(1);
}
TEST_F(BlobStoreTest, ChunkRead3) {
InitSourceBufferToFill(0);
WriteTestBlock();
ChunkReadTest(3);
}
TEST_F(BlobStoreTest, ChunkRead4) {
InitSourceBufferToFill(1);
WriteTestBlock();
ChunkReadTest(4);
}
TEST_F(BlobStoreTest, ChunkRead5) {
InitSourceBufferToFill(0xff);
WriteTestBlock();
ChunkReadTest(5);
}
TEST_F(BlobStoreTest, ChunkRead16) {
InitSourceBufferToRandom(0x86);
WriteTestBlock();
ChunkReadTest(16);
}
TEST_F(BlobStoreTest, ChunkRead64) {
InitSourceBufferToRandom(0x9);
WriteTestBlock();
ChunkReadTest(64);
}
TEST_F(BlobStoreTest, ChunkReadFull) {
InitSourceBufferToRandom(0x9);
WriteTestBlock();
ChunkReadTest(kBlobDataSize);
}
TEST_F(BlobStoreTest, PartialBufferThenClose) {
// Do write of only a partial chunk, which will only have bytes in buffer
// (none written to flash) at close.
size_t data_bytes = 12;
InitSourceBufferToRandom(0x111, data_bytes);
WriteTestBlock(data_bytes);
// Do write with several full chunks and then some partial.
data_bytes = 158;
InitSourceBufferToRandom(0x3222, data_bytes);
}
// Test to do write/close, write/close multiple times.
TEST_F(BlobStoreTest, MultipleWrites) {
InitSourceBufferToRandom(0x1121);
WriteTestBlock();
InitSourceBufferToRandom(0x515);
WriteTestBlock();
InitSourceBufferToRandom(0x4321);
WriteTestBlock();
}
} // namespace
} // namespace pw::blob_store