Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

487 lines
14 KiB

Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
  1. #include <unity.h>
  2. #include <Arduino.h>
  3. #pragma GCC diagnostic warning "-Wall"
  4. #pragma GCC diagnostic warning "-Wextra"
  5. #pragma GCC diagnostic warning "-Wstrict-aliasing"
  6. #pragma GCC diagnostic warning "-Wpointer-arith"
  7. #pragma GCC diagnostic warning "-Wstrict-overflow=5"
  8. #include <settings_embedis.h>
  9. #include <array>
  10. #include <algorithm>
  11. #include <numeric>
  12. namespace settings {
  13. namespace embedis {
  14. template <typename T>
  15. struct StaticArrayStorage {
  16. explicit StaticArrayStorage(T& blob) :
  17. _blob(blob),
  18. _size(blob.size())
  19. {}
  20. uint8_t read(size_t index) {
  21. TEST_ASSERT_LESS_THAN(_size, index);
  22. return _blob[index];
  23. }
  24. void write(size_t index, uint8_t value) {
  25. TEST_ASSERT_LESS_THAN(_size, index);
  26. _blob[index] = value;
  27. }
  28. void commit() {
  29. }
  30. T& _blob;
  31. const size_t _size;
  32. };
  33. } // namespace embedis
  34. } // namespace settings
  35. template <size_t Size>
  36. struct StorageHandler {
  37. using array_type = std::array<uint8_t, Size>;
  38. using storage_type = settings::embedis::StaticArrayStorage<array_type>;
  39. using kvs_type = settings::embedis::KeyValueStore<storage_type>;
  40. StorageHandler() :
  41. kvs(std::move(storage_type{blob}), 0, Size)
  42. {
  43. blob.fill(0xff);
  44. }
  45. array_type blob;
  46. kvs_type kvs;
  47. };
  48. // generate stuff depending on the mode
  49. // - Indexed: key1:val1, key2:val2, ...
  50. // - IncreasingLength: k:v, kk:vv, ...
  51. struct TestSequentialKvGenerator {
  52. using kv = std::pair<String, String>;
  53. enum class Mode {
  54. Indexed,
  55. IncreasingLength
  56. };
  57. TestSequentialKvGenerator() = default;
  58. explicit TestSequentialKvGenerator(Mode mode) :
  59. _mode(mode)
  60. {}
  61. const kv& next() {
  62. auto index = _index++;
  63. _current.first = "";
  64. _current.second = "";
  65. switch (_mode) {
  66. case Mode::Indexed:
  67. _current.first = String("key") + String(index);
  68. _current.second = String("val") + String(index);
  69. break;
  70. case Mode::IncreasingLength: {
  71. size_t sizes = _index;
  72. _current.first.reserve(sizes);
  73. _current.second.reserve(sizes);
  74. do {
  75. _current.first += "k";
  76. _current.second += "v";
  77. } while (--sizes);
  78. break;
  79. }
  80. }
  81. TEST_ASSERT(_last.first != _current.first);
  82. TEST_ASSERT(_last.second != _current.second);
  83. return (_last = _current);
  84. }
  85. std::vector<kv> make(size_t size) {;
  86. std::vector<kv> res;
  87. for (size_t index = 0; index < size; ++index) {
  88. res.push_back(next());
  89. }
  90. return res;
  91. }
  92. kv _current;
  93. kv _last;
  94. Mode _mode { Mode::Indexed };
  95. size_t _index { 0 };
  96. };
  97. // ----------------------------------------------------------------------------
  98. using TestStorageHandler = StorageHandler<1024>;
  99. template <typename T>
  100. void check_kv(T& instance, const String& key, const String& value) {
  101. auto result = instance.kvs.get(key);
  102. TEST_ASSERT_MESSAGE(static_cast<bool>(result), key.c_str());
  103. TEST_ASSERT(result.value.length());
  104. TEST_ASSERT_EQUAL_STRING(value.c_str(), result.value.c_str());
  105. };
  106. void test_sizes() {
  107. // empty storage is still manageble, it just does not work :)
  108. {
  109. StorageHandler<0> empty;
  110. TEST_ASSERT_EQUAL(0, empty.kvs.count());
  111. TEST_ASSERT_FALSE(empty.kvs.set("cannot", "happen"));
  112. TEST_ASSERT_FALSE(static_cast<bool>(empty.kvs.get("cannot")));
  113. }
  114. // some hard-coded estimates to notify us about internal changes
  115. {
  116. StorageHandler<16> instance;
  117. TEST_ASSERT_EQUAL(0, instance.kvs.count());
  118. TEST_ASSERT_EQUAL(16, instance.kvs.available());
  119. TEST_ASSERT_EQUAL(0, settings::embedis::estimate("", "123456"));
  120. TEST_ASSERT_EQUAL(16, settings::embedis::estimate("123456", "123456"));
  121. TEST_ASSERT_EQUAL(10, settings::embedis::estimate("123", "123"));
  122. TEST_ASSERT_EQUAL(9, settings::embedis::estimate("345", ""));
  123. }
  124. }
  125. void test_longkey() {
  126. TestStorageHandler instance;
  127. const auto estimate = instance.kvs.size() - 6;
  128. String key;
  129. key.reserve(estimate);
  130. for (size_t n = 0; n < estimate; ++n) {
  131. key += 'a';
  132. }
  133. TEST_ASSERT(instance.kvs.set(key, ""));
  134. auto result = instance.kvs.get(key);
  135. TEST_ASSERT(static_cast<bool>(result));
  136. }
  137. void test_perseverance() {
  138. // ensure we can handle setting the same key
  139. using storage_type = StorageHandler<128>;
  140. using blob_type = decltype(std::declval<storage_type>().blob);
  141. // xxx: implementation detail?
  142. // can we avoid blob modification when value is the same as the existing one
  143. {
  144. storage_type instance;
  145. blob_type original(instance.blob);
  146. TEST_ASSERT(instance.kvs.set("key", "value"));
  147. TEST_ASSERT(instance.kvs.set("another", "keyvalue"));
  148. TEST_ASSERT(original != instance.blob);
  149. blob_type snapshot(instance.blob);
  150. TEST_ASSERT(instance.kvs.set("key", "value"));
  151. TEST_ASSERT(snapshot == instance.blob);
  152. }
  153. // xxx: pointless implementation detail?
  154. // can we re-use existing 'value' storage and avoid data-shift
  155. {
  156. storage_type instance;
  157. blob_type original(instance.blob);
  158. // insert in a specific order, change middle
  159. TEST_ASSERT(instance.kvs.set("aaa", "bbb"));
  160. TEST_ASSERT(instance.kvs.set("cccc", "dd"));
  161. TEST_ASSERT(instance.kvs.set("ee", "fffff"));
  162. TEST_ASSERT(instance.kvs.set("cccc", "ff"));
  163. TEST_ASSERT(original != instance.blob);
  164. blob_type before(instance.blob);
  165. // purge, insert again with updated values
  166. TEST_ASSERT(instance.kvs.del("aaa"));
  167. TEST_ASSERT(instance.kvs.del("cccc"));
  168. TEST_ASSERT(instance.kvs.del("ee"));
  169. TEST_ASSERT(instance.kvs.set("aaa", "bbb"));
  170. TEST_ASSERT(instance.kvs.set("cccc", "ff"));
  171. TEST_ASSERT(instance.kvs.set("ee", "fffff"));
  172. blob_type after(instance.blob);
  173. TEST_ASSERT(original != before);
  174. TEST_ASSERT(original != after);
  175. TEST_ASSERT(before == after);
  176. }
  177. }
  178. template <size_t Size>
  179. struct test_overflow_runner {
  180. void operator ()() const {
  181. StorageHandler<Size> instance;
  182. TEST_ASSERT(instance.kvs.set("a", "b"));
  183. TEST_ASSERT(instance.kvs.set("c", "d"));
  184. TEST_ASSERT_EQUAL(2, instance.kvs.count());
  185. TEST_ASSERT_FALSE(instance.kvs.set("e", "f"));
  186. TEST_ASSERT(instance.kvs.del("a"));
  187. TEST_ASSERT_EQUAL(1, instance.kvs.count());
  188. TEST_ASSERT(instance.kvs.set("e", "f"));
  189. TEST_ASSERT_EQUAL(2, instance.kvs.count());
  190. check_kv(instance, "e", "f");
  191. check_kv(instance, "c", "d");
  192. }
  193. };
  194. void test_overflow() {
  195. // slightly more that available, but we cannot fit the key
  196. test_overflow_runner<16>();
  197. // no more space
  198. test_overflow_runner<12>();
  199. }
  200. void test_small_gaps() {
  201. // ensure we can intemix empty and non-empty values
  202. TestStorageHandler instance;
  203. TEST_ASSERT(instance.kvs.set("key", "value"));
  204. TEST_ASSERT(instance.kvs.set("empty", ""));
  205. TEST_ASSERT(instance.kvs.set("empty_again", ""));
  206. TEST_ASSERT(instance.kvs.set("finally", "avalue"));
  207. auto check_empty = [&instance](const String& key) {
  208. auto result = instance.kvs.get(key);
  209. TEST_ASSERT(static_cast<bool>(result));
  210. TEST_ASSERT_FALSE(result.value.length());
  211. };
  212. check_empty("empty_again");
  213. check_empty("empty");
  214. check_empty("empty_again");
  215. check_empty("empty");
  216. auto check_value = [&instance](const String& key, const String& value) {
  217. auto result = instance.kvs.get(key);
  218. TEST_ASSERT(static_cast<bool>(result));
  219. TEST_ASSERT(result.value.length());
  220. TEST_ASSERT_EQUAL_STRING(value.c_str(), result.value.c_str());
  221. };
  222. check_value("finally", "avalue");
  223. check_value("key", "value");
  224. }
  225. void test_remove_randomized() {
  226. // ensure we can remove keys in any order
  227. // 8 seems like a good number to stop on, 9 will spend ~10seconds
  228. // TODO: seems like a good start benchmarking read / write performance?
  229. constexpr size_t KeysNumber = 8;
  230. TestSequentialKvGenerator generator(TestSequentialKvGenerator::Mode::IncreasingLength);
  231. auto kvs = generator.make(KeysNumber);
  232. // generate indexes array to allow us to reference keys at random
  233. TestStorageHandler instance;
  234. std::array<size_t, KeysNumber> indexes;
  235. std::iota(indexes.begin(), indexes.end(), 0);
  236. // - insert keys sequentially
  237. // - remove keys based on the order provided by next_permutation()
  238. size_t index = 0;
  239. do {
  240. TEST_ASSERT(0 == instance.kvs.count());
  241. for (auto& kv : kvs) {
  242. TEST_ASSERT(instance.kvs.set(kv.first, kv.second));
  243. }
  244. for (auto index : indexes) {
  245. auto key = kvs[index].first;
  246. TEST_ASSERT(static_cast<bool>(instance.kvs.get(key)));
  247. TEST_ASSERT(instance.kvs.del(key));
  248. TEST_ASSERT_FALSE(static_cast<bool>(instance.kvs.get(key)));
  249. }
  250. index++;
  251. } while (std::next_permutation(indexes.begin(), indexes.end()));
  252. String message("- keys: ");
  253. message += KeysNumber;
  254. message += ", permutations: ";
  255. message += index;
  256. TEST_MESSAGE(message.c_str());
  257. }
  258. void test_basic() {
  259. TestStorageHandler instance;
  260. constexpr size_t KeysNumber = 5;
  261. // ensure insert works
  262. TestSequentialKvGenerator generator;
  263. auto kvs = generator.make(KeysNumber);
  264. for (auto& kv : kvs) {
  265. instance.kvs.set(kv.first, kv.second);
  266. }
  267. // and we can retrieve keys back
  268. for (auto& kv : kvs) {
  269. auto result = instance.kvs.get(kv.first);
  270. TEST_ASSERT(static_cast<bool>(result));
  271. TEST_ASSERT_EQUAL_STRING(kv.second.c_str(), result.value.c_str());
  272. }
  273. }
  274. void test_storage() {
  275. constexpr size_t Size = 32;
  276. StorageHandler<Size> instance;
  277. // empty keys are invalid
  278. TEST_ASSERT_FALSE(instance.kvs.set("", "value1"));
  279. TEST_ASSERT_FALSE(instance.kvs.del(""));
  280. // ...and both keys are not yet set
  281. TEST_ASSERT_FALSE(instance.kvs.del("key1"));
  282. TEST_ASSERT_FALSE(instance.kvs.del("key2"));
  283. // some different ways to set keys
  284. TEST_ASSERT(instance.kvs.set("key1", "value0"));
  285. TEST_ASSERT_EQUAL(1, instance.kvs.count());
  286. TEST_ASSERT(instance.kvs.set("key1", "value1"));
  287. TEST_ASSERT_EQUAL(1, instance.kvs.count());
  288. TEST_ASSERT(instance.kvs.set("key2", "value_old"));
  289. TEST_ASSERT_EQUAL(2, instance.kvs.count());
  290. TEST_ASSERT(instance.kvs.set("key2", "value2"));
  291. TEST_ASSERT_EQUAL(2, instance.kvs.count());
  292. auto kvsize = settings::embedis::estimate("key1", "value1");
  293. TEST_ASSERT_EQUAL((Size - (2 * kvsize)), instance.kvs.available());
  294. // checking keys one by one by using a separate kvs object,
  295. // working on the same underlying data-store
  296. using storage_type = decltype(instance)::storage_type;
  297. using kvs_type = decltype(instance)::kvs_type;
  298. // - ensure we can operate with storage offsets
  299. // - test for internal length optimization that will overwrite the key in-place
  300. // - make sure we did not break the storage above
  301. // storage_type accepts reference to the blob, so we can seamlessly use the same
  302. // underlying data storage and share it between kvs instances
  303. {
  304. kvs_type slice(storage_type(instance.blob), (Size - kvsize), Size);
  305. TEST_ASSERT_EQUAL(1, slice.count());
  306. TEST_ASSERT_EQUAL(kvsize, slice.size());
  307. TEST_ASSERT_EQUAL(0, slice.available());
  308. auto result = slice.get("key1");
  309. TEST_ASSERT(static_cast<bool>(result));
  310. TEST_ASSERT_EQUAL_STRING("value1", result.value.c_str());
  311. }
  312. // ensure that right offset also works
  313. {
  314. kvs_type slice(storage_type(instance.blob), 0, (Size - kvsize));
  315. TEST_ASSERT_EQUAL(1, slice.count());
  316. TEST_ASSERT_EQUAL((Size - kvsize), slice.size());
  317. TEST_ASSERT_EQUAL((Size - kvsize - kvsize), slice.available());
  318. auto result = slice.get("key2");
  319. TEST_ASSERT(static_cast<bool>(result));
  320. TEST_ASSERT_EQUAL_STRING("value2", result.value.c_str());
  321. }
  322. // ensure offset does not introduce offset bugs
  323. // for instance, test in-place key overwrite by moving left boundary 2 bytes to the right
  324. {
  325. const auto available = instance.kvs.available();
  326. const auto offset = 2;
  327. TEST_ASSERT_GREATER_OR_EQUAL(offset, available);
  328. kvs_type slice(storage_type(instance.blob), offset, Size);
  329. TEST_ASSERT_EQUAL(2, slice.count());
  330. auto key1 = slice.get("key1");
  331. TEST_ASSERT(static_cast<bool>(key1));
  332. String updated(key1.value);
  333. for (size_t index = 0; index < key1.value.length(); ++index) {
  334. updated[index] = 'A';
  335. }
  336. TEST_ASSERT(slice.set("key1", updated));
  337. TEST_ASSERT(slice.set("key2", updated));
  338. TEST_ASSERT_EQUAL(2, slice.count());
  339. auto check_key1 = slice.get("key1");
  340. TEST_ASSERT(static_cast<bool>(check_key1));
  341. TEST_ASSERT_EQUAL_STRING(updated.c_str(), check_key1.value.c_str());
  342. auto check_key2 = slice.get("key2");
  343. TEST_ASSERT(static_cast<bool>(check_key2));
  344. TEST_ASSERT_EQUAL_STRING(updated.c_str(), check_key2.value.c_str());
  345. TEST_ASSERT_EQUAL(available - offset, slice.available());
  346. }
  347. }
  348. void test_keys_iterator() {
  349. constexpr size_t Size = 32;
  350. StorageHandler<Size> instance;
  351. TEST_ASSERT_EQUAL(Size, instance.kvs.available());
  352. TEST_ASSERT_EQUAL(Size, instance.kvs.size());
  353. TEST_ASSERT(instance.kvs.set("key", "value"));
  354. TEST_ASSERT(instance.kvs.set("another", "thing"));
  355. // ensure we get the same order of keys when iterating via foreach
  356. std::vector<String> keys;
  357. instance.kvs.foreach([&keys](decltype(instance)::kvs_type::KeyValueResult&& kv) {
  358. keys.push_back(kv.key.read());
  359. });
  360. TEST_ASSERT(instance.kvs.keys() == keys);
  361. TEST_ASSERT_EQUAL(2, keys.size());
  362. TEST_ASSERT_EQUAL(2, instance.kvs.count());
  363. TEST_ASSERT_EQUAL_STRING("key", keys[0].c_str());
  364. TEST_ASSERT_EQUAL_STRING("another", keys[1].c_str());
  365. }
  366. int main(int argc, char** argv) {
  367. UNITY_BEGIN();
  368. RUN_TEST(test_storage);
  369. RUN_TEST(test_keys_iterator);
  370. RUN_TEST(test_basic);
  371. RUN_TEST(test_remove_randomized);
  372. RUN_TEST(test_small_gaps);
  373. RUN_TEST(test_overflow);
  374. RUN_TEST(test_perseverance);
  375. RUN_TEST(test_longkey);
  376. RUN_TEST(test_sizes);
  377. return UNITY_END();
  378. }