blob: 99b76d2e78f4aa0d52575ec508d2c0ac5eb0d9e2 [file] [log] [blame]
Wyatt Hepler495b6ee2020-02-12 18:58:01 -08001// Copyright 2020 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
Wyatt Hepler0af6ad92020-02-13 15:54:46 -080015#include <cstdlib>
Wyatt Hepler495b6ee2020-02-12 18:58:01 -080016#include <random>
Wyatt Hepler0af6ad92020-02-13 15:54:46 -080017#include <set>
Wyatt Hepler495b6ee2020-02-12 18:58:01 -080018#include <string>
19#include <string_view>
20#include <unordered_map>
Wyatt Hepler0af6ad92020-02-13 15:54:46 -080021#include <unordered_set>
Wyatt Hepler495b6ee2020-02-12 18:58:01 -080022
23#define DUMP_KVS_CONTENTS 0
24
25#if DUMP_KVS_CONTENTS
26#include <iostream>
27#endif // DUMP_KVS_CONTENTS
28
29#include "gtest/gtest.h"
30#include "pw_kvs/crc16_checksum.h"
31#include "pw_kvs/in_memory_fake_flash.h"
Wyatt Heplerbdd8e5a2020-02-20 19:27:26 -080032#include "pw_kvs/internal/entry.h"
Wyatt Hepler495b6ee2020-02-12 18:58:01 -080033#include "pw_kvs/key_value_store.h"
Wyatt Hepler0af6ad92020-02-13 15:54:46 -080034#include "pw_log/log.h"
Wyatt Hepler495b6ee2020-02-12 18:58:01 -080035#include "pw_span/span.h"
36
37namespace pw::kvs {
38namespace {
39
40using std::byte;
41
Wyatt Hepler38ce30f2020-02-19 11:48:31 -080042constexpr size_t kMaxEntries = 256;
43constexpr size_t kMaxUsableSectors = 256;
44
Wyatt Hepler495b6ee2020-02-12 18:58:01 -080045constexpr std::string_view kChars =
46 "abcdefghijklmnopqrstuvwxyz"
47 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
48 "0123456789";
49
50struct TestParameters {
51 size_t sector_size;
52 size_t sector_count;
53 size_t sector_alignment;
54 size_t partition_start_sector;
55 size_t partition_sector_count;
56 size_t partition_alignment;
57};
58
David Rogersc8fe1f52020-02-27 14:04:08 -080059enum Options {
60 kNone,
61 kReinit,
62 kReinitWithFullGC,
63 kReinitWithPartialGC,
64};
65
Wyatt Hepler0af6ad92020-02-13 15:54:46 -080066template <typename T>
67std::set<T> difference(const std::set<T> lhs, const std::set<T> rhs) {
68 std::set<T> diff;
69 std::set_difference(lhs.begin(),
70 lhs.end(),
71 rhs.begin(),
72 rhs.end(),
73 std::inserter(diff, diff.begin()));
74
75 return diff;
76}
77
Wyatt Hepler495b6ee2020-02-12 18:58:01 -080078template <const TestParameters& kParams>
79class KvsTester {
80 public:
Wyatt Hepler88adfe82020-02-20 19:33:27 -080081 static constexpr EntryFormat kFormat{.magic = 0xBAD'C0D3,
82 .checksum = nullptr};
Wyatt Hepler495b6ee2020-02-12 18:58:01 -080083
84 KvsTester()
85 : partition_(&flash_,
86 kParams.partition_start_sector,
87 kParams.partition_sector_count,
88 kParams.partition_alignment),
89 kvs_(&partition_, kFormat) {
90 EXPECT_EQ(Status::OK, partition_.Erase());
91 Status result = kvs_.Init();
92 EXPECT_EQ(Status::OK, result);
93
94 if (!result.ok()) {
95 std::abort();
96 }
97 }
98
Wyatt Hepler0af6ad92020-02-13 15:54:46 -080099 ~KvsTester() { CompareContents(); }
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800100
Wyatt Heplere2a36a22020-02-20 17:01:38 -0800101 void Test_RandomValidInputs(int iterations,
102 uint_fast32_t seed,
David Rogersc8fe1f52020-02-27 14:04:08 -0800103 Options options) {
Wyatt Heplere2a36a22020-02-20 17:01:38 -0800104 std::mt19937 random(seed);
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800105 std::uniform_int_distribution<unsigned> distro;
106 auto random_int = [&] { return distro(random); };
107
108 auto random_string = [&](size_t length) {
109 std::string value;
110 for (size_t i = 0; i < length; ++i) {
111 value.push_back(kChars[random_int() % kChars.size()]);
112 }
113 return value;
114 };
115
116 for (int i = 0; i < iterations; ++i) {
David Rogersc8fe1f52020-02-27 14:04:08 -0800117 if (options != kNone && random_int() % 10 == 0) {
Wyatt Heplere2a36a22020-02-20 17:01:38 -0800118 Init();
119 }
120
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800121 // One out of 4 times, delete a key.
122 if (random_int() % 4 == 0) {
123 // Either delete a non-existent key or delete an existing one.
124 if (empty() || random_int() % 8 == 0) {
125 Delete("not_a_key" + std::to_string(random_int()));
126 } else {
Wyatt Heplere541e072020-02-14 09:10:53 -0800127 Delete(RandomPresentKey());
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800128 }
129 } else {
130 std::string key;
131
132 // Either add a new key or replace an existing one.
133 if (empty() || random_int() % 2 == 0) {
Wyatt Hepler1fc11042020-02-19 17:17:51 -0800134 key = random_string(random_int() %
135 (internal::Entry::kMaxKeyLength + 1));
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800136 } else {
Wyatt Heplere541e072020-02-14 09:10:53 -0800137 key = RandomPresentKey();
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800138 }
139
140 Put(key, random_string(random_int() % kMaxValueLength));
141 }
David Rogerscd87c322020-02-27 14:04:08 -0800142
David Rogersc8fe1f52020-02-27 14:04:08 -0800143 if (options == kReinitWithFullGC && random_int() % 250 == 0) {
David Rogerscd87c322020-02-27 14:04:08 -0800144 GCFull();
David Rogersc8fe1f52020-02-27 14:04:08 -0800145 } else if (options == kReinitWithPartialGC && random_int() % 40 == 0) {
David Rogerscd87c322020-02-27 14:04:08 -0800146 GCPartial();
147 }
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800148 }
149 }
150
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800151 void Test_Put() {
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800152 Put("base_key", "base_value");
153 for (int i = 0; i < 100; ++i) {
154 Put("other_key", std::to_string(i));
155 }
156 for (int i = 0; i < 100; ++i) {
157 Put("key_" + std::to_string(i), std::to_string(i));
158 }
159 }
160
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800161 void Test_PutAndDelete_RelocateDeletedEntriesShouldStayDeleted() {
162 for (int i = 0; i < 100; ++i) {
163 std::string str = "key_" + std::to_string(i);
164 Put(str, std::string(kMaxValueLength, '?'));
165 Delete(str);
166 }
167 }
168
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800169 private:
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800170 void CompareContents() {
171#if DUMP_KVS_CONTENTS
172 std::set<std::string> map_keys, kvs_keys;
173
174 std::cout << "/==============================================\\\n";
175 std::cout << "KVS EXPECTED CONTENTS\n";
176 std::cout << "------------------------------------------------\n";
177 std::cout << "Entries: " << map_.size() << '\n';
178 std::cout << "------------------------------------------------\n";
179 for (const auto& [key, value] : map_) {
180 std::cout << key << " = " << value << '\n';
181 map_keys.insert(key);
182 }
183 std::cout << "\\===============================================/\n";
184
185 std::cout << "/==============================================\\\n";
186 std::cout << "KVS ACTUAL CONTENTS\n";
187 std::cout << "------------------------------------------------\n";
188 std::cout << "Entries: " << kvs_.size() << '\n';
189 std::cout << "------------------------------------------------\n";
190 for (const auto& item : kvs_) {
191 std::cout << item.key() << " = " << item.ValueSize().size() << " B\n";
192 kvs_keys.insert(std::string(item.key()));
193 }
194 std::cout << "\\===============================================/\n";
195
196 auto missing_from_kvs = difference(map_keys, kvs_keys);
197
198 if (!missing_from_kvs.empty()) {
199 std::cout << "MISSING FROM KVS: " << missing_from_kvs.size() << '\n';
200 for (auto& key : missing_from_kvs) {
201 std::cout << key << '\n';
202 }
203 }
204
205 auto missing_from_map = difference(kvs_keys, map_keys);
206 if (!missing_from_map.empty()) {
207 std::cout << "MISSING FROM MAP: " << missing_from_map.size() << '\n';
208 for (auto& key : missing_from_map) {
209 std::cout << key << '\n';
210 }
211 }
212#endif // DUMP_KVS_CONTENTS
213
214 EXPECT_EQ(map_.size(), kvs_.size());
215
216 size_t count = 0;
217
218 for (auto& item : kvs_) {
219 count += 1;
220
221 auto map_entry = map_.find(std::string(item.key()));
222 if (map_entry == map_.end()) {
Wyatt Heplera00d1ef2020-02-14 14:31:26 -0800223 PW_LOG_CRITICAL("Entry %s missing from map", item.key());
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800224 } else if (map_entry != map_.end()) {
225 EXPECT_EQ(map_entry->first, item.key());
226
227 char value[kMaxValueLength + 1] = {};
228 EXPECT_EQ(Status::OK,
229 item.Get(as_writable_bytes(span(value))).status());
230 EXPECT_EQ(map_entry->second, std::string(value));
231 }
232 }
233
234 EXPECT_EQ(count, map_.size());
235 }
236
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800237 // Adds a key to the KVS, if there is room for it.
238 void Put(const std::string& key, const std::string& value) {
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800239 StartOperation("Put", key);
240 EXPECT_LE(value.size(), kMaxValueLength);
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800241
242 Status result = kvs_.Put(key, as_bytes(span(value)));
243
Wyatt Hepler1fc11042020-02-19 17:17:51 -0800244 if (key.empty() || key.size() > internal::Entry::kMaxKeyLength) {
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800245 EXPECT_EQ(Status::INVALID_ARGUMENT, result);
Wyatt Hepler38ce30f2020-02-19 11:48:31 -0800246 } else if (map_.size() == kvs_.max_size()) {
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800247 EXPECT_EQ(Status::RESOURCE_EXHAUSTED, result);
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800248 } else if (result == Status::RESOURCE_EXHAUSTED) {
249 EXPECT_FALSE(map_.empty());
250 } else if (result.ok()) {
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800251 map_[key] = value;
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800252 deleted_.erase(key);
253 } else {
254 PW_LOG_CRITICAL("Put: unhandled result %s", result.str());
255 std::abort();
256 }
257
258 FinishOperation("Put", result, key);
259
260 if (kvs_.size() != map_.size()) {
261 PW_LOG_CRITICAL("Put: size mismatch; expected %zu, actual %zu",
262 map_.size(),
263 kvs_.size());
264 std::abort();
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800265 }
266 }
267
268 // Deletes a key from the KVS if it is present.
269 void Delete(const std::string& key) {
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800270 StartOperation("Delete", key);
271
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800272 Status result = kvs_.Delete(key);
273
Wyatt Hepler1fc11042020-02-19 17:17:51 -0800274 if (key.empty() || key.size() > internal::Entry::kMaxKeyLength) {
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800275 EXPECT_EQ(Status::INVALID_ARGUMENT, result);
276 } else if (map_.count(key) == 0) {
277 EXPECT_EQ(Status::NOT_FOUND, result);
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800278 } else if (result.ok()) {
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800279 map_.erase(key);
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800280
281 if (deleted_.count(key) > 0u) {
282 PW_LOG_CRITICAL("Deleted key that was already deleted %s", key.c_str());
283 std::abort();
284 }
285
286 deleted_.insert(key);
Wyatt Heplere2a36a22020-02-20 17:01:38 -0800287 } else if (result == Status::RESOURCE_EXHAUSTED) {
288 PW_LOG_WARN("Delete: RESOURCE_EXHAUSTED could not delete key %s",
289 key.c_str());
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800290 } else {
Wyatt Heplere2a36a22020-02-20 17:01:38 -0800291 PW_LOG_CRITICAL("Delete: unhandled result \"%s\"", result.str());
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800292 std::abort();
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800293 }
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800294 FinishOperation("Delete", result, key);
295 }
296
Wyatt Heplere2a36a22020-02-20 17:01:38 -0800297 void Init() {
298 StartOperation("Init", "");
299 Status status = kvs_.Init();
300 EXPECT_EQ(Status::OK, status);
301 FinishOperation("Init", status, "");
302 }
303
David Rogerscd87c322020-02-27 14:04:08 -0800304 void GCFull() {
305 StartOperation("GCFull", "");
306 Status status = kvs_.GarbageCollectFull();
307 EXPECT_EQ(Status::OK, status);
308 KeyValueStore::StorageStats post_stats = kvs_.GetStorageStats();
309 EXPECT_EQ(post_stats.reclaimable_bytes, 0U);
310 FinishOperation("GCFull", status, "");
311 }
312
313 void GCPartial() {
314 StartOperation("GCPartial", "");
315 KeyValueStore::StorageStats pre_stats = kvs_.GetStorageStats();
316 Status status = kvs_.GarbageCollectPartial();
317 EXPECT_EQ(Status::OK, status);
318 KeyValueStore::StorageStats post_stats = kvs_.GetStorageStats();
319 if (pre_stats.reclaimable_bytes != 0) {
320 EXPECT_LT(post_stats.reclaimable_bytes, pre_stats.reclaimable_bytes);
321 } else {
322 EXPECT_EQ(post_stats.reclaimable_bytes, 0U);
323 }
324 FinishOperation("GCPartial", status, "");
325 }
326
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800327 void StartOperation(const std::string& operation, const std::string& key) {
328 count_ += 1;
Wyatt Heplera00d1ef2020-02-14 14:31:26 -0800329 PW_LOG_DEBUG(
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800330 "[%3u] START %s for '%s'", count_, operation.c_str(), key.c_str());
331 }
332
333 void FinishOperation(const std::string& operation,
334 Status result,
335 const std::string& key) {
Wyatt Heplera00d1ef2020-02-14 14:31:26 -0800336 PW_LOG_DEBUG("[%3u] FINISH %s <%s> for '%s'",
337 count_,
338 operation.c_str(),
339 result.str(),
340 key.c_str());
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800341 }
342
343 bool empty() const { return map_.empty(); }
344
Wyatt Heplere541e072020-02-14 09:10:53 -0800345 std::string RandomPresentKey() const {
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800346 return map_.empty() ? "" : map_.begin()->second;
347 }
348
349 static constexpr size_t kMaxValueLength = 64;
350
Wyatt Heplercdd6dfc2020-02-18 12:04:04 -0800351 static FakeFlashBuffer<kParams.sector_size, kParams.sector_count> flash_;
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800352 FlashPartition partition_;
353
Wyatt Hepler38ce30f2020-02-19 11:48:31 -0800354 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800355 std::unordered_map<std::string, std::string> map_;
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800356 std::unordered_set<std::string> deleted_;
357 unsigned count_ = 0;
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800358};
359
360template <const TestParameters& kParams>
Wyatt Heplercdd6dfc2020-02-18 12:04:04 -0800361FakeFlashBuffer<kParams.sector_size, kParams.sector_count>
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800362 KvsTester<kParams>::flash_ =
Wyatt Heplercdd6dfc2020-02-18 12:04:04 -0800363 FakeFlashBuffer<kParams.sector_size, kParams.sector_count>(
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800364 kParams.sector_alignment);
365
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800366#define _TEST(fixture, test, ...) \
Wyatt Heplere2a36a22020-02-20 17:01:38 -0800367 _TEST_VARIANT(fixture, test, test, __VA_ARGS__)
368
369#define _TEST_VARIANT(fixture, test, variant, ...) \
370 TEST_F(fixture, test##variant) { tester_.Test_##test(__VA_ARGS__); }
Wyatt Hepler0af6ad92020-02-13 15:54:46 -0800371
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800372// Defines a test fixture that runs all tests against a flash with the specified
373// parameters.
David Rogersc8fe1f52020-02-27 14:04:08 -0800374#define RUN_TESTS_WITH_PARAMETERS(name, ...) \
375 class name : public ::testing::Test { \
376 protected: \
377 static constexpr TestParameters kParams = {__VA_ARGS__}; \
378 \
379 KvsTester<kParams> tester_; \
380 }; \
381 /* Run each test defined in the KvsTester class with these parameters. */ \
382 _TEST(name, Put); \
383 _TEST(name, PutAndDelete_RelocateDeletedEntriesShouldStayDeleted); \
384 _TEST_VARIANT(name, RandomValidInputs, 1, 1000, 6006411, kNone); \
385 _TEST_VARIANT(name, RandomValidInputs, 1WithReinit, 1000, 6006411, kReinit); \
386 _TEST_VARIANT(name, RandomValidInputs, 2, 100, 123, kNone); \
387 _TEST_VARIANT(name, RandomValidInputs, 2WithReinit, 100, 123, kReinit); \
388 _TEST_VARIANT(name, \
389 RandomValidInputs, \
390 1ReinitFullGC, \
391 1000, \
392 6006411, \
393 kReinitWithFullGC); \
394 _TEST_VARIANT( \
395 name, RandomValidInputs, 2ReinitFullGC, 1000, 123, kReinitWithFullGC); \
396 _TEST_VARIANT(name, \
397 RandomValidInputs, \
398 1ReinitPartialGC, \
399 100, \
400 6006411, \
401 kReinitWithPartialGC); \
402 _TEST_VARIANT(name, \
403 RandomValidInputs, \
404 2ReinitPartialGC, \
405 200, \
406 123, \
407 kReinitWithPartialGC); \
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800408 static_assert(true, "Don't forget a semicolon!")
409
410RUN_TESTS_WITH_PARAMETERS(Basic,
411 .sector_size = 4 * 1024,
412 .sector_count = 4,
413 .sector_alignment = 16,
414 .partition_start_sector = 0,
415 .partition_sector_count = 4,
416 .partition_alignment = 16);
417
Wyatt Heplere2a36a22020-02-20 17:01:38 -0800418RUN_TESTS_WITH_PARAMETERS(LotsOfSmallSectors,
419 .sector_size = 160,
420 .sector_count = 100,
421 .sector_alignment = 32,
422 .partition_start_sector = 5,
423 .partition_sector_count = 95,
424 .partition_alignment = 32);
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800425
Wyatt Heplere2a36a22020-02-20 17:01:38 -0800426RUN_TESTS_WITH_PARAMETERS(OnlyTwoSectors,
427 .sector_size = 4 * 1024,
428 .sector_count = 20,
429 .sector_alignment = 16,
430 .partition_start_sector = 18,
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800431 .partition_sector_count = 2,
Wyatt Heplere2a36a22020-02-20 17:01:38 -0800432 .partition_alignment = 64);
Wyatt Hepler495b6ee2020-02-12 18:58:01 -0800433
434} // namespace
435} // namespace pw::kvs