// 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 #include #include #include #include "gtest/gtest.h" #include "pw_blob_store/blob_store.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" namespace pw::blob_store { namespace { class DeferredWriteTest : public ::testing::Test { protected: DeferredWriteTest() : flash_(kFlashAlignment), partition_(&flash_) {} void InitFlashToErased() { ASSERT_EQ(OkStatus(), partition_.Erase()); } void InitFlashToRandom(uint64_t seed) { random::XorShiftStarRng64 rng(seed); StatusWithSize sws = rng.Get(flash_.buffer()); ASSERT_EQ(OkStatus(), sws.status()); ASSERT_EQ(sws.size(), flash_.buffer().size()); } void InitBufferToRandom(uint64_t seed) { random::XorShiftStarRng64 rng(seed); StatusWithSize sws = rng.Get(buffer_); ASSERT_EQ(OkStatus(), sws.status()); ASSERT_EQ(sws.size(), buffer_.size()); } void InitBufferToFill(char fill) { ASSERT_EQ(OkStatus(), partition_.Erase()); std::memset(buffer_.data(), fill, buffer_.size()); } // Fill the source buffer with random pattern based on given seed, written to // BlobStore in specified chunk size. void ChunkWriteTest(size_t chunk_size, size_t flush_interval, bool explicit_discard = false, bool explicit_erase = false) { constexpr size_t kWriteSize = 64; kvs::ChecksumCrc16 checksum; size_t bytes_since_flush = 0; char name[16] = {}; snprintf(name, sizeof(name), "Blob%u", static_cast(chunk_size)); BlobStoreBuffer blob( name, partition_, &checksum, kvs::TestKvs(), kWriteSize); EXPECT_EQ(OkStatus(), blob.Init()); BlobStore::DeferredWriterWithBuffer writer(blob); EXPECT_EQ(OkStatus(), writer.Open()); if (explicit_discard) { EXPECT_EQ(OkStatus(), writer.Discard()); } if (explicit_erase) { EXPECT_EQ(OkStatus(), writer.Erase()); } ByteSpan source = buffer_; while (source.size_bytes() > 0) { const size_t write_size = std::min(source.size_bytes(), chunk_size); PW_LOG_DEBUG("Do write of %u bytes, %u bytes remain", static_cast(write_size), static_cast(source.size_bytes())); ASSERT_EQ(OkStatus(), writer.Write(source.first(write_size))); // TODO: Add check that the write did not go to flash yet. source = source.subspan(write_size); bytes_since_flush += write_size; if (bytes_since_flush >= flush_interval) { bytes_since_flush = 0; ASSERT_EQ(OkStatus(), writer.Flush()); } } EXPECT_EQ(OkStatus(), writer.Close()); // Use reader to check for valid data. BlobStore::BlobReader reader(blob); ASSERT_EQ(OkStatus(), reader.Open()); Result result = reader.GetMemoryMappedBlob(); ASSERT_TRUE(result.ok()); VerifyFlash(result.value()); EXPECT_EQ(OkStatus(), reader.Close()); } void VerifyFlash(ConstByteSpan verify_bytes) { // Should be defined as same size. EXPECT_EQ(buffer_.size(), flash_.buffer().size_bytes()); // Can't allow it to march off the end of buffer_. ASSERT_LE(verify_bytes.size_bytes(), buffer_.size()); for (size_t i = 0; i < verify_bytes.size_bytes(); i++) { EXPECT_EQ(buffer_[i], verify_bytes[i]); } } static constexpr size_t kFlashAlignment = 16; static constexpr size_t kSectorSize = 1024; static constexpr size_t kSectorCount = 4; static constexpr size_t kBufferSize = 2 * kSectorSize; kvs::FakeFlashMemoryBuffer flash_; kvs::FlashPartition partition_; std::array buffer_; }; TEST_F(DeferredWriteTest, ChunkWrite1) { InitFlashToErased(); InitBufferToRandom(0x8675309); ChunkWriteTest(1, 16); } TEST_F(DeferredWriteTest, ChunkWrite2) { InitFlashToRandom(0x2283); InitBufferToRandom(0x8675); ChunkWriteTest(2, 16); } TEST_F(DeferredWriteTest, ChunkWrite3) { InitFlashToErased(); InitBufferToFill(0); ChunkWriteTest(3, 16); } TEST_F(DeferredWriteTest, ChunkWrite4) { InitFlashToErased(); InitBufferToFill(1); ChunkWriteTest(4, 64); } TEST_F(DeferredWriteTest, ChunkWrite5) { InitFlashToErased(); InitBufferToFill(0xff); ChunkWriteTest(5, 64); } TEST_F(DeferredWriteTest, ChunkWrite16) { InitFlashToErased(); InitBufferToRandom(0x86); ChunkWriteTest(16, 128); } TEST_F(DeferredWriteTest, ChunkWrite64) { InitFlashToRandom(0x9223); InitBufferToRandom(0x9); ChunkWriteTest(64, 128); } TEST_F(DeferredWriteTest, ChunkWrite64FullBufferFill) { InitFlashToErased(); InitBufferToRandom(0x9); ChunkWriteTest(64, kBufferSize); } TEST_F(DeferredWriteTest, ChunkWrite256) { InitFlashToErased(); InitBufferToRandom(0x12345678); ChunkWriteTest(256, 256); } TEST_F(DeferredWriteTest, ChunkWriteDiscard16) { InitFlashToErased(); InitBufferToRandom(0x86); // Test with a discard of an invalid blob and erased flash. ChunkWriteTest(16, 128, true); // Test with a discard of a valid blob. ChunkWriteTest(16, 128, true); // Test with a discard of an current blob with corrupted date. InitFlashToRandom(0x9223); ChunkWriteTest(16, 128, true); } TEST_F(DeferredWriteTest, ChunkWriteErase16) { InitFlashToErased(); InitBufferToRandom(0x1286); // Test with an erase of an invalid blob and erased flash. ChunkWriteTest(16, 128, false, true); // Test with an erase of a valid blob. ChunkWriteTest(16, 128, false, true); // Test with an erase of an current blob with corrupted date. InitFlashToRandom(0x9223); ChunkWriteTest(16, 128, false, true); } } // namespace } // namespace pw::blob_store