pw_kvs: Set KVS test to use any flash partition
Break out EmptyInitializedKvs to a seperate file and setup to use a
flash partition that is give by a getter method. The getter is defined
seperatly to allow reuse of the test file in other tests.
Add several test flash partitions and setup KVS and flash tests to use
them.
Change-Id: I8e28d71b3528b471c79518e658cb30c33d2b30c6
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/13620
Commit-Queue: David Rogers <davidrogers@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
diff --git a/pw_kvs/BUILD b/pw_kvs/BUILD
index dcf7f66..ac6038e 100644
--- a/pw_kvs/BUILD
+++ b/pw_kvs/BUILD
@@ -94,21 +94,50 @@
)
pw_cc_library(
- name = "flash_partition_test",
+ name = "fake_flash_small_partition",
srcs = [
- "flash_partition_test.cc",
+ "fake_flash_test_partition.cc",
],
hdrs = [
- "public/pw_kvs/flash_partition_test.h",
+ "public/pw_kvs/flash_test_partition.h",
],
deps = [
":pw_kvs",
- "//pw_log",
- "//pw_span",
+ ":fake_flash",
],
)
pw_cc_library(
+ name = "fake_flash_64_aligned_partition",
+ srcs = [
+ "fake_flash_test_partition.cc",
+ ],
+ hdrs = [
+ "public/pw_kvs/flash_test_partition.h",
+ ],
+ deps = [
+ ":pw_kvs",
+ ":fake_flash",
+ ],
+ defines = [ "PW_FLASH_TEST_ALIGNMENT=64" ],
+)
+
+pw_cc_library(
+ name = "fake_flash_256_aligned_partition",
+ srcs = [
+ "fake_flash_test_partition.cc",
+ ],
+ hdrs = [
+ "public/pw_kvs/flash_test_partition.h",
+ ],
+ deps = [
+ ":pw_kvs",
+ ":fake_flash",
+ ],
+ defines = [ "PW_FLASH_TEST_ALIGNMENT=256" ],
+)
+
+pw_cc_library(
name = "test_utils",
hdrs = [
"pw_kvs_private/byte_utils.h",
@@ -186,14 +215,39 @@
)
pw_cc_test(
- name = "fake_flash_partition_test",
- srcs = ["fake_flash_partition_test.cc"],
+ name = "flash_partition_small_test",
+ srcs = ["flash_partition_test.cc"],
deps = [
":pw_kvs",
- ":flash_partition_test",
+ ":fake_flash_small_partition",
"//pw_log:backend",
"//pw_unit_test",
],
+ defines = [ "PW_FLASH_TEST_ITERATIONS=100" ],
+)
+
+pw_cc_test(
+ name = "flash_partition_64_alignment_test",
+ srcs = ["flash_partition_test.cc"],
+ deps = [
+ ":pw_kvs",
+ ":fake_flash_64_aligned_partition",
+ "//pw_log:backend",
+ "//pw_unit_test",
+ ],
+ defines = [ "PW_FLASH_TEST_ITERATIONS=100" ],
+)
+
+pw_cc_test(
+ name = "flash_partition_256_alignment_test",
+ srcs = ["flash_partition_test.cc"],
+ deps = [
+ ":pw_kvs",
+ ":fake_flash_256_aligned_partition",
+ "//pw_log:backend",
+ "//pw_unit_test",
+ ],
+ defines = [ "PW_FLASH_TEST_ITERATIONS=100" ],
)
pw_cc_test(
@@ -214,6 +268,60 @@
)
pw_cc_test(
+ name = "key_value_store_small_flash_test",
+ srcs = ["key_value_store_initialized_test.cc"],
+ deps = [
+ ":crc16",
+ ":pw_kvs",
+ ":fake_flash_small_partition",
+ ":test_utils",
+ "//pw_checksum",
+ "//pw_log:backend",
+ "//pw_log:facade",
+ "//pw_span",
+ "//pw_status",
+ "//pw_string",
+ "//pw_unit_test",
+ ],
+)
+
+pw_cc_test(
+ name = "key_value_store_64_alignment_flash_test",
+ srcs = ["key_value_store_initialized_test.cc"],
+ deps = [
+ ":crc16",
+ ":pw_kvs",
+ ":fake_flash_64_aligned_partition",
+ ":test_utils",
+ "//pw_checksum",
+ "//pw_log:backend",
+ "//pw_log:facade",
+ "//pw_span",
+ "//pw_status",
+ "//pw_string",
+ "//pw_unit_test",
+ ],
+)
+
+pw_cc_test(
+ name = "key_value_store_256_alignment_flash_test",
+ srcs = ["key_value_store_initialized_test.cc"],
+ deps = [
+ ":crc16",
+ ":pw_kvs",
+ ":fake_flash_256_aligned_partition",
+ ":test_utils",
+ "//pw_checksum",
+ "//pw_log:backend",
+ "//pw_log:facade",
+ "//pw_span",
+ "//pw_status",
+ "//pw_string",
+ "//pw_unit_test",
+ ],
+)
+
+pw_cc_test(
name = "key_value_store_binary_format_test",
srcs = [
"key_value_store_binary_format_test.cc",
diff --git a/pw_kvs/BUILD.gn b/pw_kvs/BUILD.gn
index bb380e3..294214a 100644
--- a/pw_kvs/BUILD.gn
+++ b/pw_kvs/BUILD.gn
@@ -28,6 +28,7 @@
"public/pw_kvs/alignment.h",
"public/pw_kvs/checksum.h",
"public/pw_kvs/flash_memory.h",
+ "public/pw_kvs/flash_test_partition.h",
"public/pw_kvs/format.h",
"public/pw_kvs/io.h",
"public/pw_kvs/key_value_store.h",
@@ -84,15 +85,68 @@
deps = [ dir_pw_log ]
}
-pw_source_set("flash_partition_test") {
- public = [ "public/pw_kvs/flash_partition_test.h" ]
- sources = [ "flash_partition_test.cc" ]
+pw_source_set("fake_flash_small_partition") {
+ public_configs = [ ":default_config" ]
+ public = [ "public/pw_kvs/flash_test_partition.h" ]
+ sources = [ "fake_flash_test_partition.cc" ]
+ deps = [
+ ":fake_flash",
+ dir_pw_kvs,
+ ]
+}
+
+pw_source_set("fake_flash_64_aligned_partition") {
+ public_configs = [ ":default_config" ]
+ public = [ "public/pw_kvs/flash_test_partition.h" ]
+ sources = [ "fake_flash_test_partition.cc" ]
+ deps = [
+ ":fake_flash",
+ dir_pw_kvs,
+ ]
+ defines = [ "PW_FLASH_TEST_ALIGNMENT=64" ]
+}
+
+pw_source_set("fake_flash_256_aligned_partition") {
+ public_configs = [ ":default_config" ]
+ public = [ "public/pw_kvs/flash_test_partition.h" ]
+ sources = [ "fake_flash_test_partition.cc" ]
+ deps = [
+ ":fake_flash",
+ dir_pw_kvs,
+ ]
+ defines = [ "PW_FLASH_TEST_ALIGNMENT=256" ]
+}
+
+pw_source_set("flash_partition_test_100_iterations") {
deps = [
dir_pw_kvs,
dir_pw_log,
- dir_pw_span,
dir_pw_unit_test,
]
+ sources = [ "flash_partition_test.cc" ]
+ defines = [ "PW_FLASH_TEST_ITERATIONS=100" ]
+}
+
+pw_source_set("flash_partition_test_2_iterations") {
+ deps = [
+ dir_pw_kvs,
+ dir_pw_log,
+ dir_pw_unit_test,
+ ]
+ sources = [ "flash_partition_test.cc" ]
+ defines = [ "PW_FLASH_TEST_ITERATIONS=2" ]
+}
+
+pw_source_set("key_value_store_initialized_test") {
+ deps = [
+ ":crc16",
+ ":pw_kvs",
+ ":test_utils",
+ dir_pw_checksum,
+ dir_pw_log,
+ dir_pw_unit_test,
+ ]
+ sources = [ "key_value_store_initialized_test.cc" ]
}
pw_source_set("test_utils") {
@@ -119,8 +173,13 @@
":checksum_test",
":entry_test",
":entry_cache_test",
- ":fake_flash_partition_test",
+ ":flash_partition_small_test",
+ ":flash_partition_64_alignment_test",
+ ":flash_partition_256_alignment_test",
":key_value_store_test",
+ ":key_value_store_small_flash_test",
+ ":key_value_store_64_alignment_flash_test",
+ ":key_value_store_256_alignment_flash_test",
":key_value_store_binary_format_test",
":key_value_store_fuzz_test",
":key_value_store_map_test",
@@ -162,13 +221,31 @@
sources = [ "entry_cache_test.cc" ]
}
-pw_test("fake_flash_partition_test") {
+pw_test("flash_partition_small_test") {
deps = [
":fake_flash",
- ":flash_partition_test",
+ ":fake_flash_small_partition",
+ ":flash_partition_test_100_iterations",
dir_pw_log,
]
- sources = [ "fake_flash_partition_test.cc" ]
+}
+
+pw_test("flash_partition_64_alignment_test") {
+ deps = [
+ ":fake_flash",
+ ":fake_flash_64_aligned_partition",
+ ":flash_partition_test_100_iterations",
+ dir_pw_log,
+ ]
+}
+
+pw_test("flash_partition_256_alignment_test") {
+ deps = [
+ ":fake_flash",
+ ":fake_flash_256_aligned_partition",
+ ":flash_partition_test_100_iterations",
+ dir_pw_log,
+ ]
}
pw_test("key_value_store_test") {
@@ -183,6 +260,27 @@
sources = [ "key_value_store_test.cc" ]
}
+pw_test("key_value_store_small_flash_test") {
+ deps = [
+ ":fake_flash_small_partition",
+ ":key_value_store_initialized_test",
+ ]
+}
+
+pw_test("key_value_store_64_alignment_flash_test") {
+ deps = [
+ ":fake_flash_64_aligned_partition",
+ ":key_value_store_initialized_test",
+ ]
+}
+
+pw_test("key_value_store_256_alignment_flash_test") {
+ deps = [
+ ":fake_flash_256_aligned_partition",
+ ":key_value_store_initialized_test",
+ ]
+}
+
pw_test("key_value_store_binary_format_test") {
deps = [
":crc16",
diff --git a/pw_kvs/fake_flash_partition_test.cc b/pw_kvs/fake_flash_partition_test.cc
deleted file mode 100644
index 8b28011..0000000
--- a/pw_kvs/fake_flash_partition_test.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-// 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 "gtest/gtest.h"
-#include "pw_kvs/fake_flash_memory.h"
-#include "pw_kvs/flash_memory.h"
-#include "pw_kvs/flash_partition_test.h"
-#include "pw_log/log.h"
-
-namespace pw::kvs::PartitionTest {
-namespace {
-
-TEST(FakeFlashPartitionTest, FillTest16) {
- FakeFlashMemoryBuffer<512, 8> flash(16);
- FlashPartition test_partition(&flash);
-
- // WriteTest(test_partition);
-}
-
-TEST(FakeFlashPartitionTest, FillTest64) {
- FakeFlashMemoryBuffer<512, 8> flash(64);
- FlashPartition test_partition(&flash);
-
- // WriteTest(test_partition);
-}
-
-TEST(FakeFlashPartitionTest, FillTest256) {
- FakeFlashMemoryBuffer<512, 8> flash(256);
- FlashPartition test_partition(&flash);
-
- // WriteTest(test_partition);
-}
-
-TEST(FakeFlashPartitionTest, EraseTest) {
- FakeFlashMemoryBuffer<512, 8> flash(16);
- FlashPartition test_partition(&flash);
-
- // EraseTest(test_partition);
-}
-
-TEST(FakeFlashPartitionTest, ReadOnlyTest) {
- FakeFlashMemoryBuffer<512, 8> flash(16);
- FlashPartition test_partition(
- &flash, 0, flash.sector_count(), 0, PartitionPermission::kReadOnly);
-
- // ReadOnlyTest(test_partition);
-}
-
-} // namespace
-} // namespace pw::kvs::PartitionTest
diff --git a/pw_kvs/fake_flash_test_partition.cc b/pw_kvs/fake_flash_test_partition.cc
new file mode 100644
index 0000000..fab7e38
--- /dev/null
+++ b/pw_kvs/fake_flash_test_partition.cc
@@ -0,0 +1,48 @@
+// 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_kvs/fake_flash_memory.h"
+#include "pw_kvs/flash_memory.h"
+#include "pw_kvs/flash_test_partition.h"
+
+namespace pw::kvs {
+
+namespace {
+
+#ifndef PW_FLASH_TEST_SECTORS
+#define PW_FLASH_TEST_SECTORS 6U
+#endif // PW_FLASH_TEST_SECTORS
+
+#ifndef PW_FLASH_TEST_SECTOR_SIZE
+#define PW_FLASH_TEST_SECTOR_SIZE (4 * 1024U)
+#endif // PW_FLASH_TEST_SECTOR_SIZE
+
+#ifndef PW_FLASH_TEST_ALIGNMENT
+#define PW_FLASH_TEST_ALIGNMENT 16U
+#endif // PW_FLASH_TEST_ALIGNMENT
+
+constexpr size_t kFlashTestSectors = PW_FLASH_TEST_SECTORS;
+constexpr size_t kFlashTestSectorSize = PW_FLASH_TEST_SECTOR_SIZE;
+constexpr size_t kFlashTestAlignment = PW_FLASH_TEST_ALIGNMENT;
+
+// Use 6 x 4k sectors, 16 byte alignment.
+FakeFlashMemoryBuffer<kFlashTestSectorSize, kFlashTestSectors> test_flash(
+ kFlashTestAlignment);
+FlashPartition test_partition(&test_flash);
+
+} // namespace
+
+FlashPartition& FlashTestPartition() { return test_partition; }
+
+} // namespace pw::kvs
diff --git a/pw_kvs/flash_partition_test.cc b/pw_kvs/flash_partition_test.cc
index 6b5a9ce..5e90ac9 100644
--- a/pw_kvs/flash_partition_test.cc
+++ b/pw_kvs/flash_partition_test.cc
@@ -12,21 +12,25 @@
// License for the specific language governing permissions and limitations under
// the License.
-#include "pw_kvs/flash_partition_test.h"
-
#include <span>
#include "gtest/gtest.h"
#include "pw_kvs/flash_memory.h"
+#include "pw_kvs/flash_test_partition.h"
#include "pw_kvs_private/config.h"
#include "pw_log/log.h"
namespace pw::kvs::PartitionTest {
+namespace {
+
+#ifndef PW_FLASH_TEST_ITERATIONS
+#define PW_FLASH_TEST_ITERATIONS 2
+#endif // PW_FLASH_TEST_ITERATIONS
+
+constexpr size_t kTestIterations = PW_FLASH_TEST_ITERATIONS;
constexpr size_t kTestDataSize = kMaxFlashAlignment;
-namespace {
-
void WriteData(FlashPartition& partition, uint8_t fill_byte) {
uint8_t test_data[kTestDataSize];
memset(test_data, fill_byte, sizeof(test_data));
@@ -95,74 +99,67 @@
}
}
-} // namespace
+TEST(FlashPartitionTest, FillTest) {
+ FlashPartition& test_partition = FlashTestPartition();
-void WriteTest(FlashPartition& partition, size_t test_iterations) {
- ASSERT_GE(kTestDataSize, partition.alignment_bytes());
+ ASSERT_GE(kTestDataSize, test_partition.alignment_bytes());
- for (size_t i = 0; i < test_iterations; i++) {
- WriteData(partition, 0);
- WriteData(partition, 0xff);
- WriteData(partition, 0x55);
- WriteData(partition, 0xa3);
+ for (size_t i = 0; i < kTestIterations; i++) {
+ WriteData(test_partition, 0);
+ WriteData(test_partition, 0xff);
+ WriteData(test_partition, 0x55);
+ WriteData(test_partition, 0xa3);
}
}
-void EraseTest(FlashPartition& partition) {
+TEST(FlashPartitionTest, EraseTest) {
+ FlashPartition& test_partition = FlashTestPartition();
+
static const uint8_t fill_byte = 0x55;
uint8_t test_data[kTestDataSize];
memset(test_data, fill_byte, sizeof(test_data));
- ASSERT_GE(kTestDataSize, partition.alignment_bytes());
+ ASSERT_GE(kTestDataSize, test_partition.alignment_bytes());
const size_t block_size =
- std::min(sizeof(test_data), partition.sector_size_bytes());
+ std::min(sizeof(test_data), test_partition.sector_size_bytes());
auto data_span = std::span(test_data, block_size);
- ASSERT_EQ(Status::OK, partition.Erase(0, partition.sector_count()));
+ ASSERT_EQ(Status::OK, test_partition.Erase(0, test_partition.sector_count()));
// Write to the first page of each sector.
- for (size_t sector_index = 0; sector_index < partition.sector_count();
+ for (size_t sector_index = 0; sector_index < test_partition.sector_count();
sector_index++) {
FlashPartition::Address address =
- sector_index * partition.sector_size_bytes();
+ sector_index * test_partition.sector_size_bytes();
- StatusWithSize status = partition.Write(address, as_bytes(data_span));
+ StatusWithSize status = test_partition.Write(address, as_bytes(data_span));
ASSERT_EQ(Status::OK, status.status());
ASSERT_EQ(block_size, status.size());
}
- ASSERT_EQ(Status::OK, partition.Erase());
+ ASSERT_EQ(Status::OK, test_partition.Erase());
bool is_erased;
ASSERT_EQ(Status::OK,
- partition.IsRegionErased(0, partition.size_bytes(), &is_erased));
+ test_partition.IsRegionErased(
+ 0, test_partition.size_bytes(), &is_erased));
ASSERT_EQ(true, is_erased);
// Read the first page of each sector and make sure it has been erased.
- for (size_t sector_index = 0; sector_index < partition.sector_count();
+ for (size_t sector_index = 0; sector_index < test_partition.sector_count();
sector_index++) {
FlashPartition::Address address =
- sector_index * partition.sector_size_bytes();
+ sector_index * test_partition.sector_size_bytes();
StatusWithSize status =
- partition.Read(address, data_span.size_bytes(), data_span.data());
+ test_partition.Read(address, data_span.size_bytes(), data_span.data());
ASSERT_EQ(Status::OK, status.status());
ASSERT_EQ(data_span.size_bytes(), status.size());
- ASSERT_EQ(true, partition.AppearsErased(as_bytes(data_span)));
+ ASSERT_EQ(true, test_partition.AppearsErased(as_bytes(data_span)));
}
}
-void ReadOnlyTest(FlashPartition& partition) {
- uint8_t test_data[kTestDataSize];
- auto data_span = std::span(test_data);
-
- ASSERT_EQ(Status::PERMISSION_DENIED,
- partition.Erase(0, partition.sector_count()));
-
- ASSERT_EQ(Status::PERMISSION_DENIED,
- partition.Write(0, as_bytes(data_span)).status());
-}
-
+} // namespace
} // namespace pw::kvs::PartitionTest
diff --git a/pw_kvs/key_value_store_initialized_test.cc b/pw_kvs/key_value_store_initialized_test.cc
new file mode 100644
index 0000000..17c7d60
--- /dev/null
+++ b/pw_kvs/key_value_store_initialized_test.cc
@@ -0,0 +1,502 @@
+// 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 <array>
+#include <cstdio>
+#include <cstring>
+#include <span>
+
+#include "gtest/gtest.h"
+#include "pw_checksum/ccitt_crc16.h"
+#include "pw_kvs/crc16_checksum.h"
+#include "pw_kvs/flash_memory.h"
+#include "pw_kvs/flash_test_partition.h"
+#include "pw_kvs/internal/entry.h"
+#include "pw_kvs/key_value_store.h"
+#include "pw_kvs_private/byte_utils.h"
+#include "pw_kvs_private/macros.h"
+#include "pw_log/log.h"
+#include "pw_status/status.h"
+#include "pw_string/string_builder.h"
+
+namespace pw::kvs {
+namespace {
+
+using internal::EntryHeader;
+using std::byte;
+
+constexpr size_t kMaxEntries = 256;
+constexpr size_t kMaxUsableSectors = 256;
+
+FlashPartition& test_partition = FlashTestPartition();
+
+std::array<byte, 512> buffer;
+
+size_t RoundUpForAlignment(size_t size) {
+ return AlignUp(size, test_partition.alignment_bytes());
+}
+
+// This class gives attributes of KVS that we are testing against
+class KvsAttributes {
+ public:
+ KvsAttributes(size_t key_size, size_t data_size)
+ : chunk_header_size_(RoundUpForAlignment(sizeof(EntryHeader))),
+ data_size_(RoundUpForAlignment(data_size)),
+ key_size_(RoundUpForAlignment(key_size)),
+ erase_size_(chunk_header_size_ + key_size_),
+ min_put_size_(
+ RoundUpForAlignment(chunk_header_size_ + key_size_ + data_size_)) {}
+
+ size_t ChunkHeaderSize() { return chunk_header_size_; }
+ size_t DataSize() { return data_size_; }
+ size_t KeySize() { return key_size_; }
+ size_t EraseSize() { return erase_size_; }
+ size_t MinPutSize() { return min_put_size_; }
+
+ private:
+ const size_t chunk_header_size_;
+ const size_t data_size_;
+ const size_t key_size_;
+ const size_t erase_size_;
+ const size_t min_put_size_;
+};
+
+constexpr std::array<const char*, 3> keys{"TestKey1", "Key2", "TestKey3"};
+
+ChecksumCrc16 checksum;
+constexpr EntryFormat default_format{.magic = 0xBAD'C0D3,
+ .checksum = &checksum};
+
+class EmptyInitializedKvs : public ::testing::Test {
+ protected:
+ EmptyInitializedKvs() : kvs_(&test_partition, default_format) {
+ test_partition.Erase();
+ ASSERT_EQ(Status::OK, kvs_.Init());
+ }
+
+ // Intention of this is to put and erase key-val to fill up sectors. It's a
+ // helper function in testing how KVS handles cases where flash sector is full
+ // or near full.
+ void FillKvs(const char* key, size_t size_to_fill) {
+ constexpr size_t kTestDataSize = 8;
+ KvsAttributes kvs_attr(std::strlen(key), kTestDataSize);
+ const size_t kMaxPutSize =
+ buffer.size() + kvs_attr.ChunkHeaderSize() + kvs_attr.KeySize();
+
+ ASSERT_GE(size_to_fill, kvs_attr.MinPutSize() + kvs_attr.EraseSize());
+
+ // Saving enough space to perform erase after loop
+ size_to_fill -= kvs_attr.EraseSize();
+ // We start with possible small chunk to prevent too small of a Kvs.Put() at
+ // the end.
+ size_t chunk_len =
+ std::max(kvs_attr.MinPutSize(), size_to_fill % buffer.size());
+ std::memset(buffer.data(), 0, buffer.size());
+ while (size_to_fill > 0) {
+ // Changing buffer value so put actually does something
+ buffer[0] = static_cast<byte>(static_cast<uint8_t>(buffer[0]) + 1);
+ ASSERT_EQ(Status::OK,
+ kvs_.Put(key,
+ std::span(buffer.data(),
+ chunk_len - kvs_attr.ChunkHeaderSize() -
+ kvs_attr.KeySize())));
+ size_to_fill -= chunk_len;
+ chunk_len = std::min(size_to_fill, kMaxPutSize);
+ }
+ ASSERT_EQ(Status::OK, kvs_.Delete(key));
+ }
+
+ KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
+};
+
+} // namespace
+
+TEST_F(EmptyInitializedKvs, Put_SameKeySameValueRepeatedly_AlignedEntries) {
+ std::array<char, 8> value{'v', 'a', 'l', 'u', 'e', '6', '7', '\0'};
+
+ for (int i = 0; i < 1000; ++i) {
+ ASSERT_EQ(Status::OK,
+ kvs_.Put("The Key!", std::as_bytes(std::span(value))));
+ }
+}
+
+TEST_F(EmptyInitializedKvs, Put_SameKeySameValueRepeatedly_UnalignedEntries) {
+ std::array<char, 7> value{'v', 'a', 'l', 'u', 'e', '6', '\0'};
+
+ for (int i = 0; i < 1000; ++i) {
+ ASSERT_EQ(Status::OK,
+ kvs_.Put("The Key!", std::as_bytes(std::span(value))));
+ }
+}
+
+TEST_F(EmptyInitializedKvs, Put_SameKeyDifferentValuesRepeatedly) {
+ std::array<char, 10> value{'v', 'a', 'l', 'u', 'e', '6', '7', '8', '9', '\0'};
+
+ for (int i = 0; i < 100; ++i) {
+ for (unsigned size = 0; size < value.size(); ++size) {
+ ASSERT_EQ(Status::OK, kvs_.Put("The Key!", i));
+ }
+ }
+}
+
+TEST_F(EmptyInitializedKvs, PutAndGetByValue_ConvertibleToSpan) {
+ constexpr float input[] = {1.0, -3.5};
+ ASSERT_EQ(Status::OK, kvs_.Put("key", input));
+
+ float output[2] = {};
+ ASSERT_EQ(Status::OK, kvs_.Get("key", &output));
+ EXPECT_EQ(input[0], output[0]);
+ EXPECT_EQ(input[1], output[1]);
+}
+
+TEST_F(EmptyInitializedKvs, PutAndGetByValue_Span) {
+ float input[] = {1.0, -3.5};
+ ASSERT_EQ(Status::OK, kvs_.Put("key", std::span(input)));
+
+ float output[2] = {};
+ ASSERT_EQ(Status::OK, kvs_.Get("key", &output));
+ EXPECT_EQ(input[0], output[0]);
+ EXPECT_EQ(input[1], output[1]);
+}
+
+TEST_F(EmptyInitializedKvs, PutAndGetByValue_NotConvertibleToSpan) {
+ struct TestStruct {
+ float a;
+ bool b;
+ };
+ const TestStruct input{-1234.5, true};
+
+ ASSERT_EQ(Status::OK, kvs_.Put("key", input));
+
+ TestStruct output;
+ ASSERT_EQ(Status::OK, kvs_.Get("key", &output));
+ EXPECT_EQ(input.a, output.a);
+ EXPECT_EQ(input.b, output.b);
+}
+
+TEST_F(EmptyInitializedKvs, Get_Simple) {
+ ASSERT_EQ(Status::OK,
+ kvs_.Put("Charles", std::as_bytes(std::span("Mingus"))));
+
+ char value[16];
+ auto result = kvs_.Get("Charles", std::as_writable_bytes(std::span(value)));
+ EXPECT_EQ(Status::OK, result.status());
+ EXPECT_EQ(sizeof("Mingus"), result.size());
+ EXPECT_STREQ("Mingus", value);
+}
+
+TEST_F(EmptyInitializedKvs, Get_WithOffset) {
+ ASSERT_EQ(Status::OK,
+ kvs_.Put("Charles", std::as_bytes(std::span("Mingus"))));
+
+ char value[16];
+ auto result =
+ kvs_.Get("Charles", std::as_writable_bytes(std::span(value)), 4);
+ EXPECT_EQ(Status::OK, result.status());
+ EXPECT_EQ(sizeof("Mingus") - 4, result.size());
+ EXPECT_STREQ("us", value);
+}
+
+TEST_F(EmptyInitializedKvs, Get_WithOffset_FillBuffer) {
+ ASSERT_EQ(Status::OK,
+ kvs_.Put("Charles", std::as_bytes(std::span("Mingus"))));
+
+ char value[4] = {};
+ auto result =
+ kvs_.Get("Charles", std::as_writable_bytes(std::span(value, 3)), 1);
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, result.status());
+ EXPECT_EQ(3u, result.size());
+ EXPECT_STREQ("ing", value);
+}
+
+TEST_F(EmptyInitializedKvs, Get_WithOffset_PastEnd) {
+ ASSERT_EQ(Status::OK,
+ kvs_.Put("Charles", std::as_bytes(std::span("Mingus"))));
+
+ char value[16];
+ auto result = kvs_.Get("Charles",
+ std::as_writable_bytes(std::span(value)),
+ sizeof("Mingus") + 1);
+ EXPECT_EQ(Status::OUT_OF_RANGE, result.status());
+ EXPECT_EQ(0u, result.size());
+}
+
+TEST_F(EmptyInitializedKvs, GetValue) {
+ ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
+
+ uint32_t value = 0;
+ EXPECT_EQ(Status::OK, kvs_.Get("key", &value));
+ EXPECT_EQ(uint32_t(0xfeedbeef), value);
+}
+
+TEST_F(EmptyInitializedKvs, GetValue_TooSmall) {
+ ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
+
+ uint8_t value = 0;
+ EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.Get("key", &value));
+ EXPECT_EQ(0u, value);
+}
+
+TEST_F(EmptyInitializedKvs, GetValue_TooLarge) {
+ ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
+
+ uint64_t value = 0;
+ EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.Get("key", &value));
+ EXPECT_EQ(0u, value);
+}
+
+TEST_F(EmptyInitializedKvs, Delete_GetDeletedKey_ReturnsNotFound) {
+ ASSERT_EQ(Status::OK, kvs_.Put("kEy", std::as_bytes(std::span("123"))));
+ ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
+
+ EXPECT_EQ(Status::NOT_FOUND, kvs_.Get("kEy", {}).status());
+ EXPECT_EQ(Status::NOT_FOUND, kvs_.ValueSize("kEy").status());
+}
+
+TEST_F(EmptyInitializedKvs, Delete_AddBackKey_PersistsAfterInitialization) {
+ ASSERT_EQ(Status::OK, kvs_.Put("kEy", std::as_bytes(std::span("123"))));
+ ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
+
+ EXPECT_EQ(Status::OK, kvs_.Put("kEy", std::as_bytes(std::span("45678"))));
+ char data[6] = {};
+ ASSERT_EQ(Status::OK, kvs_.Get("kEy", &data));
+ EXPECT_STREQ(data, "45678");
+
+ // Ensure that the re-added key is still present after reinitialization.
+ KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> new_kvs(&test_partition,
+ default_format);
+ ASSERT_EQ(Status::OK, new_kvs.Init());
+
+ EXPECT_EQ(Status::OK, new_kvs.Put("kEy", std::as_bytes(std::span("45678"))));
+ char new_data[6] = {};
+ EXPECT_EQ(Status::OK, new_kvs.Get("kEy", &new_data));
+ EXPECT_STREQ(data, "45678");
+}
+
+TEST_F(EmptyInitializedKvs, Delete_AllItems_KvsIsEmpty) {
+ ASSERT_EQ(Status::OK, kvs_.Put("kEy", std::as_bytes(std::span("123"))));
+ ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
+
+ EXPECT_EQ(0u, kvs_.size());
+ EXPECT_TRUE(kvs_.empty());
+}
+
+TEST_F(EmptyInitializedKvs, Collision_WithPresentKey) {
+ // Both hash to 0x19df36f0.
+ constexpr std::string_view key1 = "D4";
+ constexpr std::string_view key2 = "dFU6S";
+
+ ASSERT_EQ(Status::OK, kvs_.Put(key1, 1000));
+
+ EXPECT_EQ(Status::ALREADY_EXISTS, kvs_.Put(key2, 999));
+
+ int value = 0;
+ EXPECT_EQ(Status::OK, kvs_.Get(key1, &value));
+ EXPECT_EQ(1000, value);
+
+ EXPECT_EQ(Status::NOT_FOUND, kvs_.Get(key2, &value));
+ EXPECT_EQ(Status::NOT_FOUND, kvs_.ValueSize(key2).status());
+ EXPECT_EQ(Status::NOT_FOUND, kvs_.Delete(key2));
+}
+
+TEST_F(EmptyInitializedKvs, Collision_WithDeletedKey) {
+ // Both hash to 0x4060f732.
+ constexpr std::string_view key1 = "1U2";
+ constexpr std::string_view key2 = "ahj9d";
+
+ ASSERT_EQ(Status::OK, kvs_.Put(key1, 1000));
+ ASSERT_EQ(Status::OK, kvs_.Delete(key1));
+
+ // key2 collides with key1's tombstone.
+ EXPECT_EQ(Status::ALREADY_EXISTS, kvs_.Put(key2, 999));
+
+ int value = 0;
+ EXPECT_EQ(Status::NOT_FOUND, kvs_.Get(key1, &value));
+
+ EXPECT_EQ(Status::NOT_FOUND, kvs_.Get(key2, &value));
+ EXPECT_EQ(Status::NOT_FOUND, kvs_.ValueSize(key2).status());
+ EXPECT_EQ(Status::NOT_FOUND, kvs_.Delete(key2));
+}
+
+TEST_F(EmptyInitializedKvs, Iteration_Empty_ByReference) {
+ for (const KeyValueStore::Item& entry : kvs_) {
+ FAIL(); // The KVS is empty; this shouldn't execute.
+ static_cast<void>(entry);
+ }
+}
+
+TEST_F(EmptyInitializedKvs, Iteration_Empty_ByValue) {
+ for (KeyValueStore::Item entry : kvs_) {
+ FAIL(); // The KVS is empty; this shouldn't execute.
+ static_cast<void>(entry);
+ }
+}
+
+TEST_F(EmptyInitializedKvs, Iteration_OneItem) {
+ ASSERT_EQ(Status::OK, kvs_.Put("kEy", std::as_bytes(std::span("123"))));
+
+ for (KeyValueStore::Item entry : kvs_) {
+ EXPECT_STREQ(entry.key(), "kEy"); // Make sure null-terminated.
+
+ char temp[sizeof("123")] = {};
+ EXPECT_EQ(Status::OK, entry.Get(&temp));
+ EXPECT_STREQ("123", temp);
+ }
+}
+
+TEST_F(EmptyInitializedKvs, Iteration_GetWithOffset) {
+ ASSERT_EQ(Status::OK, kvs_.Put("key", std::as_bytes(std::span("not bad!"))));
+
+ for (KeyValueStore::Item entry : kvs_) {
+ char temp[5];
+ auto result = entry.Get(std::as_writable_bytes(std::span(temp)), 4);
+ EXPECT_EQ(Status::OK, result.status());
+ EXPECT_EQ(5u, result.size());
+ EXPECT_STREQ("bad!", temp);
+ }
+}
+
+TEST_F(EmptyInitializedKvs, Iteration_GetValue) {
+ ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
+
+ for (KeyValueStore::Item entry : kvs_) {
+ uint32_t value = 0;
+ EXPECT_EQ(Status::OK, entry.Get(&value));
+ EXPECT_EQ(uint32_t(0xfeedbeef), value);
+ }
+}
+
+TEST_F(EmptyInitializedKvs, Iteration_GetValue_TooSmall) {
+ ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
+
+ for (KeyValueStore::Item entry : kvs_) {
+ uint8_t value = 0;
+ EXPECT_EQ(Status::INVALID_ARGUMENT, entry.Get(&value));
+ EXPECT_EQ(0u, value);
+ }
+}
+
+TEST_F(EmptyInitializedKvs, Iteration_GetValue_TooLarge) {
+ ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
+
+ for (KeyValueStore::Item entry : kvs_) {
+ uint64_t value = 0;
+ EXPECT_EQ(Status::INVALID_ARGUMENT, entry.Get(&value));
+ EXPECT_EQ(0u, value);
+ }
+}
+
+TEST_F(EmptyInitializedKvs, Iteration_EmptyAfterDeletion) {
+ ASSERT_EQ(Status::OK, kvs_.Put("kEy", std::as_bytes(std::span("123"))));
+ ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
+
+ for (KeyValueStore::Item entry : kvs_) {
+ static_cast<void>(entry);
+ FAIL();
+ }
+}
+
+TEST_F(EmptyInitializedKvs, FuzzTest) {
+ if (test_partition.sector_size_bytes() < 4 * 1024 ||
+ test_partition.sector_count() < 4) {
+ PW_LOG_INFO("Sectors too small, skipping test.");
+ return; // TODO: Test could be generalized
+ }
+ const char* key1 = "Buf1";
+ const char* key2 = "Buf2";
+ const size_t kLargestBufSize = 3 * 1024;
+ static byte buf1[kLargestBufSize];
+ static byte buf2[kLargestBufSize];
+ std::memset(buf1, 1, sizeof(buf1));
+ std::memset(buf2, 2, sizeof(buf2));
+
+ // Start with things in KVS
+ ASSERT_EQ(Status::OK, kvs_.Put(key1, buf1));
+ ASSERT_EQ(Status::OK, kvs_.Put(key2, buf2));
+ for (size_t j = 0; j < keys.size(); j++) {
+ ASSERT_EQ(Status::OK, kvs_.Put(keys[j], j));
+ }
+
+ for (size_t i = 0; i < 100; i++) {
+ // Vary two sizes
+ size_t size1 = (kLargestBufSize) / (i + 1);
+ size_t size2 = (kLargestBufSize) / (100 - i);
+ for (size_t j = 0; j < 50; j++) {
+ // Rewrite a single key many times, can fill up a sector
+ ASSERT_EQ(Status::OK, kvs_.Put("some_data", j));
+ }
+ // Delete and re-add everything
+ ASSERT_EQ(Status::OK, kvs_.Delete(key1));
+ ASSERT_EQ(Status::OK, kvs_.Put(key1, std::span(buf1, size1)));
+ ASSERT_EQ(Status::OK, kvs_.Delete(key2));
+ ASSERT_EQ(Status::OK, kvs_.Put(key2, std::span(buf2, size2)));
+ for (size_t j = 0; j < keys.size(); j++) {
+ ASSERT_EQ(Status::OK, kvs_.Delete(keys[j]));
+ ASSERT_EQ(Status::OK, kvs_.Put(keys[j], j));
+ }
+
+ // Re-enable and verify
+ ASSERT_EQ(Status::OK, kvs_.Init());
+ static byte buf[4 * 1024];
+ ASSERT_EQ(Status::OK, kvs_.Get(key1, std::span(buf, size1)).status());
+ ASSERT_EQ(std::memcmp(buf, buf1, size1), 0);
+ ASSERT_EQ(Status::OK, kvs_.Get(key2, std::span(buf, size2)).status());
+ ASSERT_EQ(std::memcmp(buf2, buf2, size2), 0);
+ for (size_t j = 0; j < keys.size(); j++) {
+ size_t ret = 1000;
+ ASSERT_EQ(Status::OK, kvs_.Get(keys[j], &ret));
+ ASSERT_EQ(ret, j);
+ }
+ }
+}
+
+TEST_F(EmptyInitializedKvs, Basic) {
+ // Add some data
+ uint8_t value1 = 0xDA;
+ ASSERT_EQ(
+ Status::OK,
+ kvs_.Put(keys[0], std::as_bytes(std::span(&value1, sizeof(value1)))));
+
+ uint32_t value2 = 0xBAD0301f;
+ ASSERT_EQ(Status::OK, kvs_.Put(keys[1], value2));
+
+ // Verify data
+ uint32_t test2;
+ EXPECT_EQ(Status::OK, kvs_.Get(keys[1], &test2));
+ uint8_t test1;
+ ASSERT_EQ(Status::OK, kvs_.Get(keys[0], &test1));
+
+ EXPECT_EQ(test1, value1);
+ EXPECT_EQ(test2, value2);
+
+ // Delete a key
+ EXPECT_EQ(Status::OK, kvs_.Delete(keys[0]));
+
+ // Verify it was erased
+ EXPECT_EQ(kvs_.Get(keys[0], &test1), Status::NOT_FOUND);
+ test2 = 0;
+ ASSERT_EQ(Status::OK,
+ kvs_.Get(keys[1],
+ std::span(reinterpret_cast<byte*>(&test2), sizeof(test2)))
+ .status());
+ EXPECT_EQ(test2, value2);
+
+ // Delete other key
+ kvs_.Delete(keys[1]);
+
+ // Verify it was erased
+ EXPECT_EQ(kvs_.size(), 0u);
+}
+
+} // namespace pw::kvs
diff --git a/pw_kvs/key_value_store_test.cc b/pw_kvs/key_value_store_test.cc
index 3549e22..f08f436 100644
--- a/pw_kvs/key_value_store_test.cc
+++ b/pw_kvs/key_value_store_test.cc
@@ -30,6 +30,7 @@
#include "gtest/gtest.h"
#include "pw_checksum/ccitt_crc16.h"
#include "pw_kvs/crc16_checksum.h"
+#include "pw_kvs/fake_flash_memory.h"
#include "pw_kvs/flash_memory.h"
#include "pw_kvs/internal/entry.h"
#include "pw_kvs_private/byte_utils.h"
@@ -38,10 +39,6 @@
#include "pw_status/status.h"
#include "pw_string/string_builder.h"
-#if USE_MEMORY_BUFFER
-#include "pw_kvs/fake_flash_memory.h"
-#endif // USE_MEMORY_BUFFER
-
namespace pw::kvs {
namespace {
@@ -153,493 +150,19 @@
typedef FlashWithPartitionFake<4 * 128 /*sector size*/, 6 /*sectors*/> Flash;
-#if USE_MEMORY_BUFFER
-// Although it might be useful to test other configurations, some tests require
-// at least 3 sectors; therfore it should have this when checked in.
-FakeFlashMemoryBuffer<4 * 1024, 6> test_flash(
- 16); // 6 x 4k sectors, 16 byte alignment
-FlashPartition test_partition(&test_flash, 0, test_flash.sector_count());
FakeFlashMemoryBuffer<1024, 60> large_test_flash(8);
FlashPartition large_test_partition(&large_test_flash,
0,
large_test_flash.sector_count());
-#else // TODO: Test with real flash
-FlashPartition& test_partition = FlashExternalTestPartition();
-#endif // USE_MEMORY_BUFFER
-std::array<byte, 512> buffer;
constexpr std::array<const char*, 3> keys{"TestKey1", "Key2", "TestKey3"};
ChecksumCrc16 checksum;
constexpr EntryFormat default_format{.magic = 0xBAD'C0D3,
.checksum = &checksum};
-size_t RoundUpForAlignment(size_t size) {
- return AlignUp(size, test_partition.alignment_bytes());
-}
-
-// This class gives attributes of KVS that we are testing against
-class KvsAttributes {
- public:
- KvsAttributes(size_t key_size, size_t data_size)
- : chunk_header_size_(RoundUpForAlignment(sizeof(EntryHeader))),
- data_size_(RoundUpForAlignment(data_size)),
- key_size_(RoundUpForAlignment(key_size)),
- erase_size_(chunk_header_size_ + key_size_),
- min_put_size_(
- RoundUpForAlignment(chunk_header_size_ + key_size_ + data_size_)) {}
-
- size_t ChunkHeaderSize() { return chunk_header_size_; }
- size_t DataSize() { return data_size_; }
- size_t KeySize() { return key_size_; }
- size_t EraseSize() { return erase_size_; }
- size_t MinPutSize() { return min_put_size_; }
-
- private:
- const size_t chunk_header_size_;
- const size_t data_size_;
- const size_t key_size_;
- const size_t erase_size_;
- const size_t min_put_size_;
-};
-
-class EmptyInitializedKvs : public ::testing::Test {
- protected:
- EmptyInitializedKvs() : kvs_(&test_partition, default_format) {
- test_partition.Erase();
- ASSERT_EQ(Status::OK, kvs_.Init());
- }
-
- // Intention of this is to put and erase key-val to fill up sectors. It's a
- // helper function in testing how KVS handles cases where flash sector is full
- // or near full.
- void FillKvs(const char* key, size_t size_to_fill) {
- constexpr size_t kTestDataSize = 8;
- KvsAttributes kvs_attr(std::strlen(key), kTestDataSize);
- const size_t kMaxPutSize =
- buffer.size() + kvs_attr.ChunkHeaderSize() + kvs_attr.KeySize();
-
- ASSERT_GE(size_to_fill, kvs_attr.MinPutSize() + kvs_attr.EraseSize());
-
- // Saving enough space to perform erase after loop
- size_to_fill -= kvs_attr.EraseSize();
- // We start with possible small chunk to prevent too small of a Kvs.Put() at
- // the end.
- size_t chunk_len =
- std::max(kvs_attr.MinPutSize(), size_to_fill % buffer.size());
- std::memset(buffer.data(), 0, buffer.size());
- while (size_to_fill > 0) {
- // Changing buffer value so put actually does something
- buffer[0] = static_cast<byte>(static_cast<uint8_t>(buffer[0]) + 1);
- ASSERT_EQ(Status::OK,
- kvs_.Put(key,
- std::span(buffer.data(),
- chunk_len - kvs_attr.ChunkHeaderSize() -
- kvs_attr.KeySize())));
- size_to_fill -= chunk_len;
- chunk_len = std::min(size_to_fill, kMaxPutSize);
- }
- ASSERT_EQ(Status::OK, kvs_.Delete(key));
- }
-
- KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
-};
-
} // namespace
-TEST_F(EmptyInitializedKvs, Put_SameKeySameValueRepeatedly_AlignedEntries) {
- std::array<char, 8> value{'v', 'a', 'l', 'u', 'e', '6', '7', '\0'};
-
- for (int i = 0; i < 1000; ++i) {
- ASSERT_EQ(Status::OK,
- kvs_.Put("The Key!", std::as_bytes(std::span(value))));
- }
-}
-
-TEST_F(EmptyInitializedKvs, Put_SameKeySameValueRepeatedly_UnalignedEntries) {
- std::array<char, 7> value{'v', 'a', 'l', 'u', 'e', '6', '\0'};
-
- for (int i = 0; i < 1000; ++i) {
- ASSERT_EQ(Status::OK,
- kvs_.Put("The Key!", std::as_bytes(std::span(value))));
- }
-}
-
-TEST_F(EmptyInitializedKvs, Put_SameKeyDifferentValuesRepeatedly) {
- std::array<char, 10> value{'v', 'a', 'l', 'u', 'e', '6', '7', '8', '9', '\0'};
-
- for (int i = 0; i < 100; ++i) {
- for (unsigned size = 0; size < value.size(); ++size) {
- ASSERT_EQ(Status::OK, kvs_.Put("The Key!", i));
- }
- }
-}
-
-TEST_F(EmptyInitializedKvs, Put_MaxValueSize) {
- size_t max_value_size =
- test_partition.sector_size_bytes() - sizeof(EntryHeader) - 1;
-
- // Use the large_test_flash as a big chunk of data for the Put statement.
- ASSERT_GT(sizeof(large_test_flash), max_value_size + 2 * sizeof(EntryHeader));
- auto big_data = std::as_bytes(std::span(&large_test_flash, 1));
-
- EXPECT_EQ(Status::OK, kvs_.Put("K", big_data.subspan(0, max_value_size)));
-
- // Larger than maximum is rejected.
- EXPECT_EQ(Status::INVALID_ARGUMENT,
- kvs_.Put("K", big_data.subspan(0, max_value_size + 1)));
- EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.Put("K", big_data));
-}
-
-TEST_F(EmptyInitializedKvs, PutAndGetByValue_ConvertibleToSpan) {
- constexpr float input[] = {1.0, -3.5};
- ASSERT_EQ(Status::OK, kvs_.Put("key", input));
-
- float output[2] = {};
- ASSERT_EQ(Status::OK, kvs_.Get("key", &output));
- EXPECT_EQ(input[0], output[0]);
- EXPECT_EQ(input[1], output[1]);
-}
-
-TEST_F(EmptyInitializedKvs, PutAndGetByValue_Span) {
- float input[] = {1.0, -3.5};
- ASSERT_EQ(Status::OK, kvs_.Put("key", std::span(input)));
-
- float output[2] = {};
- ASSERT_EQ(Status::OK, kvs_.Get("key", &output));
- EXPECT_EQ(input[0], output[0]);
- EXPECT_EQ(input[1], output[1]);
-}
-
-TEST_F(EmptyInitializedKvs, PutAndGetByValue_NotConvertibleToSpan) {
- struct TestStruct {
- float a;
- bool b;
- };
- const TestStruct input{-1234.5, true};
-
- ASSERT_EQ(Status::OK, kvs_.Put("key", input));
-
- TestStruct output;
- ASSERT_EQ(Status::OK, kvs_.Get("key", &output));
- EXPECT_EQ(input.a, output.a);
- EXPECT_EQ(input.b, output.b);
-}
-
-TEST_F(EmptyInitializedKvs, Get_Simple) {
- ASSERT_EQ(Status::OK,
- kvs_.Put("Charles", std::as_bytes(std::span("Mingus"))));
-
- char value[16];
- auto result = kvs_.Get("Charles", std::as_writable_bytes(std::span(value)));
- EXPECT_EQ(Status::OK, result.status());
- EXPECT_EQ(sizeof("Mingus"), result.size());
- EXPECT_STREQ("Mingus", value);
-}
-
-TEST_F(EmptyInitializedKvs, Get_WithOffset) {
- ASSERT_EQ(Status::OK,
- kvs_.Put("Charles", std::as_bytes(std::span("Mingus"))));
-
- char value[16];
- auto result =
- kvs_.Get("Charles", std::as_writable_bytes(std::span(value)), 4);
- EXPECT_EQ(Status::OK, result.status());
- EXPECT_EQ(sizeof("Mingus") - 4, result.size());
- EXPECT_STREQ("us", value);
-}
-
-TEST_F(EmptyInitializedKvs, Get_WithOffset_FillBuffer) {
- ASSERT_EQ(Status::OK,
- kvs_.Put("Charles", std::as_bytes(std::span("Mingus"))));
-
- char value[4] = {};
- auto result =
- kvs_.Get("Charles", std::as_writable_bytes(std::span(value, 3)), 1);
- EXPECT_EQ(Status::RESOURCE_EXHAUSTED, result.status());
- EXPECT_EQ(3u, result.size());
- EXPECT_STREQ("ing", value);
-}
-
-TEST_F(EmptyInitializedKvs, Get_WithOffset_PastEnd) {
- ASSERT_EQ(Status::OK,
- kvs_.Put("Charles", std::as_bytes(std::span("Mingus"))));
-
- char value[16];
- auto result = kvs_.Get("Charles",
- std::as_writable_bytes(std::span(value)),
- sizeof("Mingus") + 1);
- EXPECT_EQ(Status::OUT_OF_RANGE, result.status());
- EXPECT_EQ(0u, result.size());
-}
-
-TEST_F(EmptyInitializedKvs, GetValue) {
- ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
-
- uint32_t value = 0;
- EXPECT_EQ(Status::OK, kvs_.Get("key", &value));
- EXPECT_EQ(uint32_t(0xfeedbeef), value);
-}
-
-TEST_F(EmptyInitializedKvs, GetValue_TooSmall) {
- ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
-
- uint8_t value = 0;
- EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.Get("key", &value));
- EXPECT_EQ(0u, value);
-}
-
-TEST_F(EmptyInitializedKvs, GetValue_TooLarge) {
- ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
-
- uint64_t value = 0;
- EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.Get("key", &value));
- EXPECT_EQ(0u, value);
-}
-
-TEST_F(EmptyInitializedKvs, Delete_GetDeletedKey_ReturnsNotFound) {
- ASSERT_EQ(Status::OK, kvs_.Put("kEy", std::as_bytes(std::span("123"))));
- ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
-
- EXPECT_EQ(Status::NOT_FOUND, kvs_.Get("kEy", {}).status());
- EXPECT_EQ(Status::NOT_FOUND, kvs_.ValueSize("kEy").status());
-}
-
-TEST_F(EmptyInitializedKvs, Delete_AddBackKey_PersistsAfterInitialization) {
- ASSERT_EQ(Status::OK, kvs_.Put("kEy", std::as_bytes(std::span("123"))));
- ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
-
- EXPECT_EQ(Status::OK, kvs_.Put("kEy", std::as_bytes(std::span("45678"))));
- char data[6] = {};
- ASSERT_EQ(Status::OK, kvs_.Get("kEy", &data));
- EXPECT_STREQ(data, "45678");
-
- // Ensure that the re-added key is still present after reinitialization.
- KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> new_kvs(&test_partition,
- default_format);
- ASSERT_EQ(Status::OK, new_kvs.Init());
-
- EXPECT_EQ(Status::OK, new_kvs.Put("kEy", std::as_bytes(std::span("45678"))));
- char new_data[6] = {};
- EXPECT_EQ(Status::OK, new_kvs.Get("kEy", &new_data));
- EXPECT_STREQ(data, "45678");
-}
-
-TEST_F(EmptyInitializedKvs, Delete_AllItems_KvsIsEmpty) {
- ASSERT_EQ(Status::OK, kvs_.Put("kEy", std::as_bytes(std::span("123"))));
- ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
-
- EXPECT_EQ(0u, kvs_.size());
- EXPECT_TRUE(kvs_.empty());
-}
-
-TEST_F(EmptyInitializedKvs, Collision_WithPresentKey) {
- // Both hash to 0x19df36f0.
- constexpr std::string_view key1 = "D4";
- constexpr std::string_view key2 = "dFU6S";
-
- ASSERT_EQ(Status::OK, kvs_.Put(key1, 1000));
-
- EXPECT_EQ(Status::ALREADY_EXISTS, kvs_.Put(key2, 999));
-
- int value = 0;
- EXPECT_EQ(Status::OK, kvs_.Get(key1, &value));
- EXPECT_EQ(1000, value);
-
- EXPECT_EQ(Status::NOT_FOUND, kvs_.Get(key2, &value));
- EXPECT_EQ(Status::NOT_FOUND, kvs_.ValueSize(key2).status());
- EXPECT_EQ(Status::NOT_FOUND, kvs_.Delete(key2));
-}
-
-TEST_F(EmptyInitializedKvs, Collision_WithDeletedKey) {
- // Both hash to 0x4060f732.
- constexpr std::string_view key1 = "1U2";
- constexpr std::string_view key2 = "ahj9d";
-
- ASSERT_EQ(Status::OK, kvs_.Put(key1, 1000));
- ASSERT_EQ(Status::OK, kvs_.Delete(key1));
-
- // key2 collides with key1's tombstone.
- EXPECT_EQ(Status::ALREADY_EXISTS, kvs_.Put(key2, 999));
-
- int value = 0;
- EXPECT_EQ(Status::NOT_FOUND, kvs_.Get(key1, &value));
-
- EXPECT_EQ(Status::NOT_FOUND, kvs_.Get(key2, &value));
- EXPECT_EQ(Status::NOT_FOUND, kvs_.ValueSize(key2).status());
- EXPECT_EQ(Status::NOT_FOUND, kvs_.Delete(key2));
-}
-
-TEST_F(EmptyInitializedKvs, Iteration_Empty_ByReference) {
- for (const KeyValueStore::Item& entry : kvs_) {
- FAIL(); // The KVS is empty; this shouldn't execute.
- static_cast<void>(entry);
- }
-}
-
-TEST_F(EmptyInitializedKvs, Iteration_Empty_ByValue) {
- for (KeyValueStore::Item entry : kvs_) {
- FAIL(); // The KVS is empty; this shouldn't execute.
- static_cast<void>(entry);
- }
-}
-
-TEST_F(EmptyInitializedKvs, Iteration_OneItem) {
- ASSERT_EQ(Status::OK, kvs_.Put("kEy", std::as_bytes(std::span("123"))));
-
- for (KeyValueStore::Item entry : kvs_) {
- EXPECT_STREQ(entry.key(), "kEy"); // Make sure null-terminated.
-
- char temp[sizeof("123")] = {};
- EXPECT_EQ(Status::OK, entry.Get(&temp));
- EXPECT_STREQ("123", temp);
- }
-}
-
-TEST_F(EmptyInitializedKvs, Iteration_GetWithOffset) {
- ASSERT_EQ(Status::OK, kvs_.Put("key", std::as_bytes(std::span("not bad!"))));
-
- for (KeyValueStore::Item entry : kvs_) {
- char temp[5];
- auto result = entry.Get(std::as_writable_bytes(std::span(temp)), 4);
- EXPECT_EQ(Status::OK, result.status());
- EXPECT_EQ(5u, result.size());
- EXPECT_STREQ("bad!", temp);
- }
-}
-
-TEST_F(EmptyInitializedKvs, Iteration_GetValue) {
- ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
-
- for (KeyValueStore::Item entry : kvs_) {
- uint32_t value = 0;
- EXPECT_EQ(Status::OK, entry.Get(&value));
- EXPECT_EQ(uint32_t(0xfeedbeef), value);
- }
-}
-
-TEST_F(EmptyInitializedKvs, Iteration_GetValue_TooSmall) {
- ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
-
- for (KeyValueStore::Item entry : kvs_) {
- uint8_t value = 0;
- EXPECT_EQ(Status::INVALID_ARGUMENT, entry.Get(&value));
- EXPECT_EQ(0u, value);
- }
-}
-
-TEST_F(EmptyInitializedKvs, Iteration_GetValue_TooLarge) {
- ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
-
- for (KeyValueStore::Item entry : kvs_) {
- uint64_t value = 0;
- EXPECT_EQ(Status::INVALID_ARGUMENT, entry.Get(&value));
- EXPECT_EQ(0u, value);
- }
-}
-
-TEST_F(EmptyInitializedKvs, Iteration_EmptyAfterDeletion) {
- ASSERT_EQ(Status::OK, kvs_.Put("kEy", std::as_bytes(std::span("123"))));
- ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
-
- for (KeyValueStore::Item entry : kvs_) {
- static_cast<void>(entry);
- FAIL();
- }
-}
-
-TEST_F(EmptyInitializedKvs, FuzzTest) {
- if (test_partition.sector_size_bytes() < 4 * 1024 ||
- test_partition.sector_count() < 4) {
- PW_LOG_INFO("Sectors too small, skipping test.");
- return; // TODO: Test could be generalized
- }
- const char* key1 = "Buf1";
- const char* key2 = "Buf2";
- const size_t kLargestBufSize = 3 * 1024;
- static byte buf1[kLargestBufSize];
- static byte buf2[kLargestBufSize];
- std::memset(buf1, 1, sizeof(buf1));
- std::memset(buf2, 2, sizeof(buf2));
-
- // Start with things in KVS
- ASSERT_EQ(Status::OK, kvs_.Put(key1, buf1));
- ASSERT_EQ(Status::OK, kvs_.Put(key2, buf2));
- for (size_t j = 0; j < keys.size(); j++) {
- ASSERT_EQ(Status::OK, kvs_.Put(keys[j], j));
- }
-
- for (size_t i = 0; i < 100; i++) {
- // Vary two sizes
- size_t size1 = (kLargestBufSize) / (i + 1);
- size_t size2 = (kLargestBufSize) / (100 - i);
- for (size_t j = 0; j < 50; j++) {
- // Rewrite a single key many times, can fill up a sector
- ASSERT_EQ(Status::OK, kvs_.Put("some_data", j));
- }
- // Delete and re-add everything
- ASSERT_EQ(Status::OK, kvs_.Delete(key1));
- ASSERT_EQ(Status::OK, kvs_.Put(key1, std::span(buf1, size1)));
- ASSERT_EQ(Status::OK, kvs_.Delete(key2));
- ASSERT_EQ(Status::OK, kvs_.Put(key2, std::span(buf2, size2)));
- for (size_t j = 0; j < keys.size(); j++) {
- ASSERT_EQ(Status::OK, kvs_.Delete(keys[j]));
- ASSERT_EQ(Status::OK, kvs_.Put(keys[j], j));
- }
-
- // Re-enable and verify
- ASSERT_EQ(Status::OK, kvs_.Init());
- static byte buf[4 * 1024];
- ASSERT_EQ(Status::OK, kvs_.Get(key1, std::span(buf, size1)).status());
- ASSERT_EQ(std::memcmp(buf, buf1, size1), 0);
- ASSERT_EQ(Status::OK, kvs_.Get(key2, std::span(buf, size2)).status());
- ASSERT_EQ(std::memcmp(buf2, buf2, size2), 0);
- for (size_t j = 0; j < keys.size(); j++) {
- size_t ret = 1000;
- ASSERT_EQ(Status::OK, kvs_.Get(keys[j], &ret));
- ASSERT_EQ(ret, j);
- }
- }
-}
-
-TEST_F(EmptyInitializedKvs, Basic) {
- // Add some data
- uint8_t value1 = 0xDA;
- ASSERT_EQ(
- Status::OK,
- kvs_.Put(keys[0], std::as_bytes(std::span(&value1, sizeof(value1)))));
-
- uint32_t value2 = 0xBAD0301f;
- ASSERT_EQ(Status::OK, kvs_.Put(keys[1], value2));
-
- // Verify data
- uint32_t test2;
- EXPECT_EQ(Status::OK, kvs_.Get(keys[1], &test2));
- uint8_t test1;
- ASSERT_EQ(Status::OK, kvs_.Get(keys[0], &test1));
-
- EXPECT_EQ(test1, value1);
- EXPECT_EQ(test2, value2);
-
- // Delete a key
- EXPECT_EQ(Status::OK, kvs_.Delete(keys[0]));
-
- // Verify it was erased
- EXPECT_EQ(kvs_.Get(keys[0], &test1), Status::NOT_FOUND);
- test2 = 0;
- ASSERT_EQ(Status::OK,
- kvs_.Get(keys[1],
- std::span(reinterpret_cast<byte*>(&test2), sizeof(test2)))
- .status());
- EXPECT_EQ(test2, value2);
-
- // Delete other key
- kvs_.Delete(keys[1]);
-
- // Verify it was erased
- EXPECT_EQ(kvs_.size(), 0u);
-}
-
TEST(InitCheck, TooFewSectors) {
// Use test flash with 1 x 4k sectors, 16 byte alignment
FakeFlashMemoryBuffer<4 * 1024, 1> test_flash(16);
@@ -848,487 +371,28 @@
EXPECT_EQ(kvs.size(), 2u);
}
-TEST_F(EmptyInitializedKvs, MaxKeyLength) {
- // Add some data
- char key[16] = "123456789abcdef"; // key length 15 (without \0)
- int value = 1;
- ASSERT_EQ(Status::OK, kvs_.Put(key, value));
+TEST(InMemoryKvs, CallingEraseTwice_NothingWrittenToFlash) {
+ // Create and erase the fake flash.
+ Flash flash;
+ ASSERT_EQ(Status::OK, flash.partition.Erase());
- // Verify data
- int test = 0;
- ASSERT_EQ(Status::OK, kvs_.Get(key, &test));
- EXPECT_EQ(test, value);
+ // Create and initialize the KVS.
+ KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
+ default_format);
+ ASSERT_OK(kvs.Init());
- // Delete a key
- EXPECT_EQ(Status::OK, kvs_.Delete(key));
-
- // Verify it was erased
- EXPECT_EQ(kvs_.Get(key, &test), Status::NOT_FOUND);
-}
-
-TEST_F(EmptyInitializedKvs, LargeBuffers) {
- // Note this assumes that no other keys larger then key0
- static_assert(sizeof(keys[0]) >= sizeof(keys[1]) &&
- sizeof(keys[0]) >= sizeof(keys[2]));
- KvsAttributes kvs_attr(std::strlen(keys[0]), buffer.size());
-
- // Verify the data will fit in this test partition. This checks that all the
- // keys chunks will fit and a header for each sector will fit. It requires 1
- // empty sector also.
- const size_t kMinSize = kvs_attr.MinPutSize() * keys.size();
- const size_t kAvailSectorSpace =
- test_partition.sector_size_bytes() * (test_partition.sector_count() - 1);
- if (kAvailSectorSpace < kMinSize) {
- PW_LOG_INFO("KVS too small, skipping test.");
- return;
- }
-
- // Add and verify
- for (unsigned add_idx = 0; add_idx < keys.size(); add_idx++) {
- std::memset(buffer.data(), add_idx, buffer.size());
- ASSERT_EQ(Status::OK, kvs_.Put(keys[add_idx], buffer));
- EXPECT_EQ(kvs_.size(), add_idx + 1);
- for (unsigned verify_idx = 0; verify_idx <= add_idx; verify_idx++) {
- std::memset(buffer.data(), 0, buffer.size());
- ASSERT_EQ(Status::OK, kvs_.Get(keys[verify_idx], buffer).status());
- for (unsigned i = 0; i < buffer.size(); i++) {
- EXPECT_EQ(static_cast<unsigned>(buffer[i]), verify_idx);
- }
- }
- }
-
- // Erase and verify
- for (unsigned erase_idx = 0; erase_idx < keys.size(); erase_idx++) {
- ASSERT_EQ(Status::OK, kvs_.Delete(keys[erase_idx]));
- EXPECT_EQ(kvs_.size(), keys.size() - erase_idx - 1);
- for (unsigned verify_idx = 0; verify_idx < keys.size(); verify_idx++) {
- std::memset(buffer.data(), 0, buffer.size());
- if (verify_idx <= erase_idx) {
- ASSERT_EQ(kvs_.Get(keys[verify_idx], buffer).status(),
- Status::NOT_FOUND);
- } else {
- ASSERT_EQ(Status::OK, kvs_.Get(keys[verify_idx], buffer).status());
- for (uint32_t i = 0; i < buffer.size(); i++) {
- EXPECT_EQ(buffer[i], static_cast<byte>(verify_idx));
- }
- }
- }
- }
-}
-
-TEST_F(EmptyInitializedKvs, Enable) {
- KvsAttributes kvs_attr(std::strlen(keys[0]), buffer.size());
-
- // Verify the data will fit in this test partition. This checks that all the
- // keys chunks will fit and a header for each sector will fit. It requires 1
- // empty sector also.
- const size_t kMinSize = kvs_attr.MinPutSize() * keys.size();
- const size_t kAvailSectorSpace =
- test_partition.sector_size_bytes() * (test_partition.sector_count() - 1);
- if (kAvailSectorSpace < kMinSize) {
- PW_LOG_INFO("KVS too small, skipping test.");
- return;
- }
-
- // Add some items
- for (unsigned add_idx = 0; add_idx < keys.size(); add_idx++) {
- std::memset(buffer.data(), add_idx, buffer.size());
- ASSERT_EQ(Status::OK, kvs_.Put(keys[add_idx], buffer));
- EXPECT_EQ(kvs_.size(), add_idx + 1);
- }
-
- // Enable different KVS which should be able to properly setup the same map
- // from what is stored in flash.
- static KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_local(
- &test_partition, default_format);
- ASSERT_EQ(Status::OK, kvs_local.Init());
- EXPECT_EQ(kvs_local.size(), keys.size());
-
- // Ensure adding to new KVS works
- uint8_t value = 0xDA;
- const char* key = "new_key";
- ASSERT_EQ(Status::OK, kvs_local.Put(key, value));
- uint8_t test;
- ASSERT_EQ(Status::OK, kvs_local.Get(key, &test));
- EXPECT_EQ(value, test);
- EXPECT_EQ(kvs_local.size(), keys.size() + 1);
-
- // Verify previous data
- for (unsigned verify_idx = 0; verify_idx < keys.size(); verify_idx++) {
- std::memset(buffer.data(), 0, buffer.size());
- ASSERT_EQ(Status::OK, kvs_local.Get(keys[verify_idx], buffer).status());
- for (uint32_t i = 0; i < buffer.size(); i++) {
- EXPECT_EQ(static_cast<unsigned>(buffer[i]), verify_idx);
- }
- }
-}
-
-TEST_F(EmptyInitializedKvs, MultiSector) {
- // Calculate number of elements to ensure multiple sectors are required.
- uint16_t add_count = (test_partition.sector_size_bytes() / buffer.size()) + 1;
-
- if (kvs_.max_size() < add_count) {
- PW_LOG_INFO("Sector size too large, skipping test.");
- return; // this chip has very large sectors, test won't work
- }
- if (test_partition.sector_count() < 3) {
- PW_LOG_INFO("Not enough sectors, skipping test.");
- return; // need at least 3 sectors for multi-sector test
- }
-
- char key[20];
- for (unsigned add_idx = 0; add_idx < add_count; add_idx++) {
- std::memset(buffer.data(), add_idx, buffer.size());
- snprintf(key, sizeof(key), "key_%u", add_idx);
- ASSERT_EQ(Status::OK, kvs_.Put(key, buffer));
- EXPECT_EQ(kvs_.size(), add_idx + 1);
- }
-
- for (unsigned verify_idx = 0; verify_idx < add_count; verify_idx++) {
- std::memset(buffer.data(), 0, buffer.size());
- snprintf(key, sizeof(key), "key_%u", verify_idx);
- ASSERT_EQ(Status::OK, kvs_.Get(key, buffer).status());
- for (uint32_t i = 0; i < buffer.size(); i++) {
- EXPECT_EQ(static_cast<unsigned>(buffer[i]), verify_idx);
- }
- }
-
- // Check erase
- for (unsigned erase_idx = 0; erase_idx < add_count; erase_idx++) {
- snprintf(key, sizeof(key), "key_%u", erase_idx);
- ASSERT_EQ(Status::OK, kvs_.Delete(key));
- EXPECT_EQ(kvs_.size(), add_count - erase_idx - 1);
- }
-}
-
-TEST_F(EmptyInitializedKvs, RewriteValue) {
- // Write first value
- const uint8_t kValue1 = 0xDA;
- const uint8_t kValue2 = 0x12;
- const char* key = "the_key";
- ASSERT_EQ(Status::OK, kvs_.Put(key, std::as_bytes(std::span(&kValue1, 1))));
-
- // Verify
- uint8_t value;
- ASSERT_EQ(
- Status::OK,
- kvs_.Get(key, std::as_writable_bytes(std::span(&value, 1))).status());
- EXPECT_EQ(kValue1, value);
-
- // Write new value for key
- ASSERT_EQ(Status::OK, kvs_.Put(key, std::as_bytes(std::span(&kValue2, 1))));
-
- // Verify
- ASSERT_EQ(
- Status::OK,
- kvs_.Get(key, std::as_writable_bytes(std::span(&value, 1))).status());
- EXPECT_EQ(kValue2, value);
-
- // Verify only 1 element exists
- EXPECT_EQ(kvs_.size(), 1u);
-}
-
-TEST_F(EmptyInitializedKvs, RepeatingValueWithOtherData) {
- std::byte set_buf[150];
- std::byte get_buf[sizeof(set_buf)];
-
- for (size_t set_index = 0; set_index < sizeof(set_buf); set_index++) {
- set_buf[set_index] = static_cast<std::byte>(set_index);
- }
-
- StatusWithSize result;
-
- // Test setting the same entry 10 times but varying the amount of data
- // that is already in env before each test
- for (size_t test_iteration = 0; test_iteration < sizeof(set_buf);
- test_iteration++) {
- // TOD0: Add KVS erase
- // Add a constant unchanging entry so that the updates are not
- // the only entries in the env. The size of this initial entry
- // we vary between no bytes to sizeof(set_buf).
- ASSERT_EQ(Status::OK,
- kvs_.Put("const_entry", std::span(set_buf, test_iteration)));
-
- // The value we read back should be the last value we set
- std::memset(get_buf, 0, sizeof(get_buf));
- result = kvs_.Get("const_entry", std::span(get_buf));
- ASSERT_EQ(Status::OK, result.status());
- ASSERT_EQ(result.size(), test_iteration);
- for (size_t j = 0; j < test_iteration; j++) {
- EXPECT_EQ(set_buf[j], get_buf[j]);
- }
-
- // Update the test entry 5 times
- static_assert(sizeof(std::byte) == sizeof(uint8_t));
- uint8_t set_entry_buf[]{1, 2, 3, 4, 5, 6, 7, 8};
- std::byte* set_entry = reinterpret_cast<std::byte*>(set_entry_buf);
- std::byte get_entry_buf[sizeof(set_entry_buf)];
- for (size_t i = 0; i < 5; i++) {
- set_entry[0] = static_cast<std::byte>(i);
- ASSERT_EQ(
- Status::OK,
- kvs_.Put("test_entry", std::span(set_entry, sizeof(set_entry_buf))));
- std::memset(get_entry_buf, 0, sizeof(get_entry_buf));
- result = kvs_.Get("test_entry", std::span(get_entry_buf));
- ASSERT_TRUE(result.ok());
- ASSERT_EQ(result.size(), sizeof(get_entry_buf));
- for (uint32_t j = 0; j < sizeof(set_entry_buf); j++) {
- EXPECT_EQ(set_entry[j], get_entry_buf[j]);
- }
- }
-
- // Check that the const entry is still present and has the right value
- std::memset(get_buf, 0, sizeof(get_buf));
- result = kvs_.Get("const_entry", std::span(get_buf));
- ASSERT_TRUE(result.ok());
- ASSERT_EQ(result.size(), test_iteration);
- for (size_t j = 0; j < test_iteration; j++) {
- EXPECT_EQ(set_buf[j], get_buf[j]);
- }
- }
-}
-
-TEST_F(EmptyInitializedKvs, OffsetRead) {
- const char* key = "the_key";
- constexpr size_t kReadSize = 16; // needs to be a multiple of alignment
- constexpr size_t kTestBufferSize = kReadSize * 10;
- ASSERT_GT(buffer.size(), kTestBufferSize);
- ASSERT_LE(kTestBufferSize, 0xFFu);
-
- // Write the entire buffer
- for (size_t i = 0; i < kTestBufferSize; i++) {
- buffer[i] = byte(i);
- }
- ASSERT_EQ(Status::OK,
- kvs_.Put(key, std::span(buffer.data(), kTestBufferSize)));
- EXPECT_EQ(kvs_.size(), 1u);
-
- // Read in small chunks and verify
- for (unsigned i = 0; i < kTestBufferSize / kReadSize; i++) {
- std::memset(buffer.data(), 0, buffer.size());
- StatusWithSize result =
- kvs_.Get(key, std::span(buffer.data(), kReadSize), i * kReadSize);
-
- ASSERT_EQ(kReadSize, result.size());
-
- // Only last iteration is OK since all remaining data was read.
- if (i == kTestBufferSize / kReadSize - 1) {
- ASSERT_EQ(Status::OK, result.status());
- } else { // RESOURCE_EXHAUSTED, since there is still data to read.
- ASSERT_EQ(Status::RESOURCE_EXHAUSTED, result.status());
- }
-
- for (unsigned j = 0; j < kReadSize; j++) {
- ASSERT_EQ(static_cast<unsigned>(buffer[j]), j + i * kReadSize);
- }
- }
-}
-
-TEST_F(EmptyInitializedKvs, MultipleRewrite) {
- // Calculate number of elements to ensure multiple sectors are required.
- unsigned add_count = (test_partition.sector_size_bytes() / buffer.size()) + 1;
-
- const char* key = "the_key";
- constexpr uint8_t kGoodVal = 0x60;
- constexpr uint8_t kBadVal = 0xBA;
- std::memset(buffer.data(), kBadVal, buffer.size());
- for (unsigned add_idx = 0; add_idx < add_count; add_idx++) {
- if (add_idx == add_count - 1) { // last value
- std::memset(buffer.data(), kGoodVal, buffer.size());
- }
- ASSERT_EQ(Status::OK, kvs_.Put(key, buffer));
- EXPECT_EQ(kvs_.size(), 1u);
- }
-
- // Verify
- std::memset(buffer.data(), 0, buffer.size());
- ASSERT_EQ(Status::OK, kvs_.Get(key, buffer).status());
- for (uint32_t i = 0; i < buffer.size(); i++) {
- ASSERT_EQ(buffer[i], static_cast<byte>(kGoodVal));
- }
-}
-
-TEST_F(EmptyInitializedKvs, FillSector) {
- ASSERT_EQ(std::strlen(keys[0]), 8U); // Easier for alignment
- ASSERT_EQ(std::strlen(keys[2]), 8U); // Easier for alignment
- constexpr size_t kTestDataSize = 8;
- KvsAttributes kvs_attr(std::strlen(keys[2]), kTestDataSize);
- int bytes_remaining = test_partition.sector_size_bytes();
- constexpr byte kKey0Pattern = byte{0xBA};
-
- std::memset(
- buffer.data(), static_cast<int>(kKey0Pattern), kvs_attr.DataSize());
- ASSERT_EQ(Status::OK,
- kvs_.Put(keys[0], std::span(buffer.data(), kvs_attr.DataSize())));
- bytes_remaining -= kvs_attr.MinPutSize();
- std::memset(buffer.data(), 1, kvs_attr.DataSize());
- ASSERT_EQ(Status::OK,
- kvs_.Put(keys[2], std::span(buffer.data(), kvs_attr.DataSize())));
- bytes_remaining -= kvs_attr.MinPutSize();
- EXPECT_EQ(kvs_.size(), 2u);
- ASSERT_EQ(Status::OK, kvs_.Delete(keys[2]));
- bytes_remaining -= kvs_attr.EraseSize();
- EXPECT_EQ(kvs_.size(), 1u);
-
- // Intentionally adding erase size to trigger sector cleanup
- bytes_remaining += kvs_attr.EraseSize();
- FillKvs(keys[2], bytes_remaining);
-
- // Verify key[0]
- std::memset(buffer.data(), 0, kvs_attr.DataSize());
- ASSERT_EQ(Status::OK,
- kvs_.Get(keys[0], std::span(buffer.data(), kvs_attr.DataSize()))
- .status());
- for (uint32_t i = 0; i < kvs_attr.DataSize(); i++) {
- EXPECT_EQ(buffer[i], kKey0Pattern);
- }
-}
-
-TEST_F(EmptyInitializedKvs, Interleaved) {
- const uint8_t kValue1 = 0xDA;
- const uint8_t kValue2 = 0x12;
- uint8_t value;
- ASSERT_EQ(Status::OK, kvs_.Put(keys[0], kValue1));
- EXPECT_EQ(kvs_.size(), 1u);
- ASSERT_EQ(Status::OK, kvs_.Delete(keys[0]));
- EXPECT_EQ(kvs_.Get(keys[0], &value), Status::NOT_FOUND);
- ASSERT_EQ(Status::OK,
- kvs_.Put(keys[1], std::as_bytes(std::span(&kValue1, 1))));
- ASSERT_EQ(Status::OK, kvs_.Put(keys[2], kValue2));
- ASSERT_EQ(Status::OK, kvs_.Delete(keys[1]));
- EXPECT_EQ(Status::OK, kvs_.Get(keys[2], &value));
- EXPECT_EQ(kValue2, value);
-
- EXPECT_EQ(kvs_.size(), 1u);
-}
-
-TEST_F(EmptyInitializedKvs, DeleteAndReinitialize) {
- // Write value
const uint8_t kValue = 0xDA;
- ASSERT_EQ(Status::OK, kvs_.Put(keys[0], kValue));
+ ASSERT_EQ(Status::OK, kvs.Put(keys[0], kValue));
+ ASSERT_EQ(Status::OK, kvs.Delete(keys[0]));
- ASSERT_EQ(Status::OK, kvs_.Delete(keys[0]));
- uint8_t value;
- ASSERT_EQ(kvs_.Get(keys[0], &value), Status::NOT_FOUND);
+ // Compare before / after checksums to verify that nothing was written.
+ const uint16_t crc = checksum::CcittCrc16(flash.memory.buffer());
- // Reset KVS, ensure captured at enable
- ASSERT_EQ(Status::OK, kvs_.Init());
+ EXPECT_EQ(kvs.Delete(keys[0]), Status::NOT_FOUND);
- ASSERT_EQ(kvs_.Get(keys[0], &value), Status::NOT_FOUND);
+ EXPECT_EQ(crc, checksum::CcittCrc16(flash.memory.buffer()));
}
-TEST_F(EmptyInitializedKvs, TemplatedPutAndGet) {
- // Store a value with the convenience method.
- const uint32_t kValue = 0x12345678;
- ASSERT_EQ(Status::OK, kvs_.Put(keys[0], kValue));
-
- // Read it back with the other convenience method.
- uint32_t value;
- ASSERT_EQ(Status::OK, kvs_.Get(keys[0], &value));
- ASSERT_EQ(kValue, value);
-
- // Make sure we cannot get something where size isn't what we expect
- const uint8_t kSmallValue = 0xBA;
- uint8_t small_value = kSmallValue;
- ASSERT_EQ(kvs_.Get(keys[0], &small_value), Status::INVALID_ARGUMENT);
- ASSERT_EQ(small_value, kSmallValue);
-}
-
-// This test is derived from bug that was discovered. Testing this corner case
-// relies on creating a new key-value just under the size that is left over in
-// the sector.
-TEST_F(EmptyInitializedKvs, FillSector2) {
- if (test_partition.sector_count() < 3) {
- PW_LOG_INFO("Not enough sectors, skipping test.");
- return; // need at least 3 sectors
- }
-
- // Start of by filling flash sector to near full
- constexpr int kHalfBufferSize = buffer.size() / 2;
- const int kSizeToFill = test_partition.sector_size_bytes() - kHalfBufferSize;
- constexpr size_t kTestDataSize = 8;
- KvsAttributes kvs_attr(std::strlen(keys[2]), kTestDataSize);
-
- FillKvs(keys[2], kSizeToFill);
-
- // Find out how much space is remaining for new key-value and confirm it
- // makes sense.
- size_t new_keyvalue_size = 0;
- size_t alignment = test_partition.alignment_bytes();
- // Starts on second sector since it will try to keep first sector free
- FlashPartition::Address read_address =
- 2 * test_partition.sector_size_bytes() - alignment;
- for (; read_address > 0; read_address -= alignment) {
- bool is_erased = false;
- ASSERT_EQ(
- Status::OK,
- test_partition.IsRegionErased(read_address, alignment, &is_erased));
- if (is_erased) {
- new_keyvalue_size += alignment;
- } else {
- break;
- }
- }
-
- size_t expected_remaining = test_partition.sector_size_bytes() - kSizeToFill;
- ASSERT_EQ(new_keyvalue_size, expected_remaining);
-
- const char* kNewKey = "NewKey";
- constexpr size_t kValueLessThanChunkHeaderSize = 2;
- constexpr auto kTestPattern = byte{0xBA};
- new_keyvalue_size -= kValueLessThanChunkHeaderSize;
- std::memset(buffer.data(), static_cast<int>(kTestPattern), new_keyvalue_size);
- ASSERT_EQ(Status::OK,
- kvs_.Put(kNewKey, std::span(buffer.data(), new_keyvalue_size)));
-
- // In failed corner case, adding new key is deceptively successful. It isn't
- // until KVS is disabled and reenabled that issue can be detected.
- ASSERT_EQ(Status::OK, kvs_.Init());
-
- // Might as well check that new key-value is what we expect it to be
- ASSERT_EQ(
- Status::OK,
- kvs_.Get(kNewKey, std::span(buffer.data(), new_keyvalue_size)).status());
- for (size_t i = 0; i < new_keyvalue_size; i++) {
- EXPECT_EQ(buffer[i], kTestPattern);
- }
-}
-
-TEST_F(EmptyInitializedKvs, ValueSize_Positive) {
- constexpr auto kData = AsBytes('h', 'i', '!');
- ASSERT_EQ(Status::OK, kvs_.Put("TheKey", kData));
-
- auto result = kvs_.ValueSize("TheKey");
-
- EXPECT_EQ(Status::OK, result.status());
- EXPECT_EQ(kData.size(), result.size());
-}
-
-TEST_F(EmptyInitializedKvs, ValueSize_Zero) {
- ASSERT_EQ(Status::OK, kvs_.Put("TheKey", std::as_bytes(std::span("123", 3))));
- auto result = kvs_.ValueSize("TheKey");
-
- EXPECT_EQ(Status::OK, result.status());
- EXPECT_EQ(3u, result.size());
-}
-
-TEST_F(EmptyInitializedKvs, ValueSize_InvalidKey) {
- EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.ValueSize("").status());
-}
-
-TEST_F(EmptyInitializedKvs, ValueSize_MissingKey) {
- EXPECT_EQ(Status::NOT_FOUND, kvs_.ValueSize("Not in there").status());
-}
-
-TEST_F(EmptyInitializedKvs, ValueSize_DeletedKey) {
- ASSERT_EQ(Status::OK, kvs_.Put("TheKey", std::as_bytes(std::span("123", 3))));
- ASSERT_EQ(Status::OK, kvs_.Delete("TheKey"));
-
- EXPECT_EQ(Status::NOT_FOUND, kvs_.ValueSize("TheKey").status());
-}
-
-#if USE_MEMORY_BUFFER
-
class LargeEmptyInitializedKvs : public ::testing::Test {
protected:
LargeEmptyInitializedKvs() : kvs_(&large_test_partition, default_format) {
@@ -1356,19 +420,29 @@
EXPECT_EQ(kvs_.size(), 1u);
}
-#endif // USE_MEMORY_BUFFER
+TEST(InMemoryKvs, Put_MaxValueSize) {
+ // Create and erase the fake flash.
+ Flash flash;
+ ASSERT_EQ(Status::OK, flash.partition.Erase());
-TEST_F(EmptyInitializedKvs, CallingEraseTwice_NothingWrittenToFlash) {
- const uint8_t kValue = 0xDA;
- ASSERT_EQ(Status::OK, kvs_.Put(keys[0], kValue));
- ASSERT_EQ(Status::OK, kvs_.Delete(keys[0]));
+ // Create and initialize the KVS.
+ KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
+ default_format);
+ ASSERT_OK(kvs.Init());
- // Compare before / after checksums to verify that nothing was written.
- const uint16_t crc = checksum::CcittCrc16(test_flash.buffer());
+ size_t max_value_size =
+ flash.partition.sector_size_bytes() - sizeof(EntryHeader) - 1;
- EXPECT_EQ(kvs_.Delete(keys[0]), Status::NOT_FOUND);
+ // Use the large_test_flash as a big chunk of data for the Put statement.
+ ASSERT_GT(sizeof(large_test_flash), max_value_size + 2 * sizeof(EntryHeader));
+ auto big_data = std::as_bytes(std::span(&large_test_flash, 1));
- EXPECT_EQ(crc, checksum::CcittCrc16(test_flash.buffer()));
+ EXPECT_EQ(Status::OK, kvs.Put("K", big_data.subspan(0, max_value_size)));
+
+ // Larger than maximum is rejected.
+ EXPECT_EQ(Status::INVALID_ARGUMENT,
+ kvs.Put("K", big_data.subspan(0, max_value_size + 1)));
+ EXPECT_EQ(Status::INVALID_ARGUMENT, kvs.Put("K", big_data));
}
} // namespace pw::kvs
diff --git a/pw_kvs/public/pw_kvs/flash_partition_test.h b/pw_kvs/public/pw_kvs/flash_test_partition.h
similarity index 71%
rename from pw_kvs/public/pw_kvs/flash_partition_test.h
rename to pw_kvs/public/pw_kvs/flash_test_partition.h
index 8c8496d..268c115 100644
--- a/pw_kvs/public/pw_kvs/flash_partition_test.h
+++ b/pw_kvs/public/pw_kvs/flash_test_partition.h
@@ -13,16 +13,10 @@
// the License.
#pragma once
-#include <cstddef>
-
#include "pw_kvs/flash_memory.h"
-namespace pw::kvs::PartitionTest {
+namespace pw::kvs {
-void WriteTest(FlashPartition& partition, size_t test_iterations = 2);
+FlashPartition& FlashTestPartition();
-void EraseTest(FlashPartition& partition);
-
-void ReadOnlyTest(FlashPartition& partition);
-
-} // namespace pw::kvs::PartitionTest
+} // namespace pw::kvs
diff --git a/pw_kvs/pw_kvs_private/config.h b/pw_kvs/pw_kvs_private/config.h
index 0976bc0..bbff197 100644
--- a/pw_kvs/pw_kvs_private/config.h
+++ b/pw_kvs/pw_kvs_private/config.h
@@ -17,9 +17,10 @@
#include <cstddef>
+// The maximum flash alignment supported
#ifndef PW_KVS_MAX_FLASH_ALIGNMENT
#define PW_KVS_MAX_FLASH_ALIGNMENT 256UL
-#endif
+#endif // PW_KVS_MAX_FLASH_ALIGNMENT
static_assert((PW_KVS_MAX_FLASH_ALIGNMENT >= 16UL),
"Max flash alignment is required to be at least 16");