pebble/tests/fw/services/settings/test_settings_file.c
2025-01-27 11:38:16 -08:00

683 lines
24 KiB
C

/*
* Copyright 2024 Google LLC
*
* 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
*
* http://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 "services/normal/settings/settings_file.h"
#include "services/normal/settings/settings_raw_iter.h"
#include "system/hexdump.h"
#include "clar.h"
#include "services/normal/filesystem/pfs.h"
#include "flash_region/flash_region.h"
#include <stdio.h>
#include <string.h>
// Stubs
////////////////////////////////////
#include "stubs_analytics.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_print.h"
#include "stubs_prompt.h"
#include "stubs_rand_ptr.h"
#include "stubs_serial.h"
#include "stubs_sleep.h"
#include "stubs_system_reset.h"
#include "stubs_task_watchdog.h"
#include "fake_rtc.h"
#include "fake_spi_flash.h"
// Tests
////////////////////////////////////
extern status_t settings_file_compact(SettingsFile *file);
void test_settings_file__initialize(void) {
fake_spi_flash_init(0, 0x1000000);
pfs_init(false);
}
void test_settings_file__cleanup(void) {}
#include <stdio.h>
#define PRIb8 "%d%d%d%d%d%d%d%d"
#define TO_BINARY(byte) \
(byte & 0x80 ? 1 : 0), \
(byte & 0x40 ? 1 : 0), \
(byte & 0x20 ? 1 : 0), \
(byte & 0x10 ? 1 : 0), \
(byte & 0x08 ? 1 : 0), \
(byte & 0x04 ? 1 : 0), \
(byte & 0x02 ? 1 : 0), \
(byte & 0x01 ? 1 : 0)
extern void pfs_debug_dump(int fd, int num_bytes);
void settings_file_hexdump(SettingsFile *file) {
pfs_seek(file->iter.fd, 0, FSeekSet);
pfs_debug_dump(file->iter.fd, 4096);
}
void settings_file_dump(SettingsFile *file) {
for (settings_raw_iter_begin(&file->iter); !settings_raw_iter_end(&file->iter);
settings_raw_iter_next(&file->iter)) {
SettingsRecordHeader hdr = file->iter.hdr;
printf("Record { last_modified: %d, ", hdr.last_modified);
printf("flags: "PRIb8", ", TO_BINARY(hdr.flags));
printf("key_hash: %"PRIu8", key_len: %d, val_len: %d }\n", hdr.key_hash, hdr.key_len, hdr.val_len);
}
}
static void verify(SettingsFile *file, uint8_t *key, int key_len,
uint8_t *val, int val_len) {
int val_len_out = settings_file_get_len(file, key, key_len);
cl_must_pass(val_len_out);
cl_assert_equal_i(val_len, val_len_out);
const bool is_immediate_delete = (val_len == 0 && DELETED_LIFETIME <= 0);
uint8_t *val_out = malloc(val_len_out);
cl_assert_equal_i(settings_file_get(file, key, key_len, val_out, val_len_out),
is_immediate_delete ? E_DOES_NOT_EXIST : S_SUCCESS);
if (!is_immediate_delete) {
cl_assert_equal_m(val, val_out, val_len);
}
free(val_out);
}
static void set_and_verify(SettingsFile *file, uint8_t *key, int key_len,
uint8_t *val, int val_len) {
cl_must_pass(settings_file_set(file, key, key_len, val, val_len));
verify(file, key, key_len, val, val_len);
}
void test_settings_file__set_get_one(void) {
printf("\nTesting setting and retreiving a single key a single time...\n");
SettingsFile file;
cl_must_pass(settings_file_open(&file, "test_file_set_get_one", 4096));
uint8_t key[4];
int key_len = sizeof(key) - 1;
memcpy(key, "key", sizeof(key));
uint8_t val[4];
int val_len = sizeof(val) - 1;
memcpy(val, "val", sizeof(val));
set_and_verify(&file, key, key_len, val, val_len);
}
void test_settings_file__set_get_one_many_times(void) {
printf("\nTesting setting and retreiving a key several times...\n");
SettingsFile file;
cl_must_pass(settings_file_open(&file, "test_file_set_get_one_many_times", 4096));
uint8_t key[4];
int key_len = sizeof(key) - 1;
memcpy(key, "key", sizeof(key));
uint8_t val[7];
int val_len = 6;
printf("Setting key 10 times and verifying we get the same value back...\n");
for (int i = 0; i < 10; i++) {
snprintf((char *)val, 7, "val%03d", i);
printf("Iteration %d val %s\n", i, val);
set_and_verify(&file, key, key_len, val, val_len);
}
settings_file_close(&file);
cl_must_pass(settings_file_open(&file, "test_file_set_get_one_many_times", 4096));
printf("Making sure we still get the right value after closing & reopening the file...\n");
verify(&file, key, key_len, val, val_len);
}
static bool prv_each_check_all_values(SettingsFile *file, SettingsRecordInfo *info, void *context) {
uint32_t desired_value = *(uint32_t *)context;
uint32_t key;
uint32_t value;
info->get_key(file, (uint8_t *)&key, sizeof(uint32_t));
info->get_val(file, (uint8_t *)&value, sizeof(uint32_t));
cl_assert(value == desired_value);
return true;
}
static void prv_test_settings_file_compaction(const bool manual) {
// If manual is enabled, then the test will force a compaction every so often
// If manual is disabled, then the test will only compact when it is required to do so.
SettingsFile file;
cl_must_pass(settings_file_open(&file, "test_file_compaction", 2048));
uint32_t value = 0;
static const uint32_t LOOPS = 10;
static const uint32_t NUMBER_ENTRIES = 50;
// set all keys to 0
for (uint32_t i = 0; i < NUMBER_ENTRIES; i++) {
settings_file_set(&file, (uint8_t *)&i, sizeof(uint32_t), (uint8_t *)&value, sizeof(uint32_t));
}
for (uint32_t i = 0; i < LOOPS; i++) {
// increment value of all NUMBER_ENTRIES entries
for (uint32_t j = 0; j < NUMBER_ENTRIES; j++) {
if (manual && (j % 10 == 0)) {
cl_must_pass(settings_file_compact(&file));
}
settings_file_get(&file, (uint8_t *)&j, sizeof(uint32_t),
(uint8_t *)&value, sizeof(uint32_t));
value += 1;
set_and_verify(&file, (uint8_t *)&j, sizeof(uint32_t), (uint8_t *)&value, sizeof(uint32_t));
}
}
// ensure all values of every entry is equal to LOOPS
uint32_t desired_value = LOOPS;
settings_file_each(&file, prv_each_check_all_values, (void *)&desired_value);
}
void test_settings_file__manual_compaction_increment(void) {
printf("\nTesting manual file compaction...\n");
const bool manual_compaction = true;
prv_test_settings_file_compaction(manual_compaction);
}
void test_settings_file__automatic_compaction_increment(void) {
printf("\nTesting automatic file compaction...\n");
const bool manual_compaction = false;
prv_test_settings_file_compaction(manual_compaction);
}
static void prv_print_stats(SettingsFile *file) {
printf("file max used space = %d\n", file->max_used_space);
printf("file max space total = %d\n", file->max_space_total);
printf("file used space = %d\n", file->used_space);
printf("file dead space = %d\n", file->dead_space);
}
void test_settings_file__compute_stats(void) {
printf("\nTesting if compute stats is equal to live stats...\n");
SettingsFile file;
const uint32_t max_used_space = 4096;
cl_must_pass(settings_file_open(&file, "test_file_max_storage", max_used_space));
uint8_t key[5];
int key_len = 4;
uint8_t val[5];
int val_len = 4;
for (int i = 0; i < 100; i++) {
prv_print_stats(&file);
snprintf((char *)key, sizeof(key), "k%03d", i);
snprintf((char *)val, sizeof(val), "v%03d", i);
printf("Iteration %d key %s val %s\n", i, key, val);
set_and_verify(&file, key, key_len, val, val_len);
// delete the first 50
if (i < 50) {
settings_file_delete(&file, key, key_len);
}
}
prv_print_stats(&file);
settings_file_compact(&file);
const SettingsFile file_copy = file;
settings_file_close(&file);
cl_must_pass(settings_file_open(&file, "test_file_max_storage", max_used_space));
cl_assert_equal_i(file.max_used_space, file_copy.max_used_space);
cl_assert_equal_i(file.max_space_total, file_copy.max_space_total);
cl_assert_equal_i(file.used_space, file_copy.used_space);
cl_assert_equal_i(file.dead_space, file_copy.dead_space);
}
void test_settings_file__max_storage(void) {
printf("\nTesting what happens when we hit the storage limits...\n");
SettingsFile file;
cl_must_pass(settings_file_open(&file, "test_file_max_storage", 4096));
uint8_t key[5];
int key_len = 4;
uint8_t val[5];
int val_len = 4;
// 255 * (8 + 4 + 4) = 4080 + 8 + 8 = 4096
for (int i = 0; i < 255; i++) {
snprintf((char *)key, sizeof(key), "k%03d", i);
snprintf((char *)val, sizeof(val), "v%03d", i);
printf("Iteration %d key %s val %s\n", i, key, val);
set_and_verify(&file, key, key_len, val, val_len);
}
prv_print_stats(&file);
printf("Making sure we handle running out of storage gracefully...\n");
memcpy(key, "k255", 5);
memcpy(val, "v255", 5);
cl_assert_equal_i(settings_file_set(&file, key, key_len, val, val_len), E_OUT_OF_STORAGE);
int val_len_out = settings_file_get_len(&file, key, key_len);
cl_must_pass(val_len_out);
uint8_t *val_out = malloc(val_len_out);
cl_assert_equal_i(settings_file_get(&file, key, key_len, val_out, val_len_out), E_DOES_NOT_EXIST);
free(val_out);
printf("Making sure we can delete when at max storage...\n");
memcpy(key, "k000", 5);
cl_must_pass(settings_file_delete(&file, key, key_len));
}
void test_settings_file__max_storage_with_delete(void) {
printf("\nTesting what happens when we hit the storage limits with deletes...\n");
SettingsFile file;
cl_must_pass(settings_file_open(&file, "test_file_max_storage", 4096));
uint8_t key[5];
int key_len = 4;
uint8_t val[5];
int val_len = 4;
// 255 * (8 + 4 + 4) = 4080 + 8 + 8 = 4096
for (int j = 0; j < 255 * 2; j++) {
prv_print_stats(&file);
int i = j % 255;
snprintf((char *)key, sizeof(key), "k%03d", i);
snprintf((char *)val, sizeof(val), "v%03d", i);
printf("Iteration %d key %s val %s\n", i, key, val);
set_and_verify(&file, key, key_len, val, val_len);
// delete the first iteration
if (j < 255) {
settings_file_delete(&file, key, key_len);
}
}
printf("Making sure we handle running out of storage gracefully...\n");
memcpy(key, "k255", 5);
memcpy(val, "v255", 5);
cl_assert_equal_i(settings_file_set(&file, key, key_len, val, val_len), E_OUT_OF_STORAGE);
int val_len_out = settings_file_get_len(&file, key, key_len);
cl_must_pass(val_len_out);
uint8_t *val_out = malloc(val_len_out);
cl_assert_equal_i(settings_file_get(&file, key, key_len, val_out, val_len_out), E_DOES_NOT_EXIST);
free(val_out);
}
void test_settings_file__used_space_tracking(void) {
printf("\nTesting used space tracking...\n");
SettingsFile file;
cl_must_pass(settings_file_open(&file, "test_file_used_space_tracking", 4096));
uint8_t key[5];
int key_len = 4;
uint8_t val[5];
int val_len = 4;
// First, fill half the file up with random keys and values. This reduces
// our usable space to slightly less than 2048 bytes.
for (int i = 0; i < 128; i++) {
snprintf((char *)key, sizeof(key), "k%03d", i);
snprintf((char *)val, sizeof(val), "v%03d", i);
printf("Iteration %d val %s\n", i, val);
set_and_verify(&file, key, key_len, val, val_len);
}
// Then, write to the same key many many times. This should only use up 16
// more bytes of the file if our used/free space tracking is working
// correctly, but may end up being counted as more if it's broken.
snprintf((char *)key, sizeof(key), "k%03d", 128);
for (int i = 0; i < 128; i++) {
snprintf((char *)val, sizeof(val), "v%03d", i);
printf("Iteration %d val %s\n", i, val);
set_and_verify(&file, key, key_len, val, val_len);
}
// Finally, try to set a key to a large value. This should succeed with
// plenty of room to spare if our space tracking is functioning correctly.
uint8_t big_val[SETTINGS_VAL_MAX_LEN] = {};
set_and_verify(&file, key, key_len, big_val, 511);
}
typedef enum {
RecordResultOld,
RecordResultNew,
RecordResultEnd,
} RecordResult;
static RecordResult write_and_change_record_aborting_after_bytes(int after_n_bytes) {
fake_spi_flash_init(0, 0x1000000);
// Wednesday (the 1st) at 00:00
// date -d "2014/01/01 00:00:00" "+%s" ==> 1388563200
fake_rtc_init(0, 1388563200);
pfs_init(false);
SettingsFile file_original;
cl_must_pass(settings_file_open(&file_original, "test_file_atomic", 4096));
uint8_t key[4];
int key_len = sizeof(key) - 1;
memcpy(key, "key", sizeof(key));
#define ORIGINAL_VALUE "original_value"
uint8_t original_value[sizeof(ORIGINAL_VALUE)];
int original_value_length = sizeof(ORIGINAL_VALUE) - 1;
memcpy(original_value, ORIGINAL_VALUE, sizeof(ORIGINAL_VALUE));
set_and_verify(&file_original, key, key_len, original_value, original_value_length);
#define NEW_VALUE "new_value"
uint8_t new_value[sizeof(NEW_VALUE)];
int new_value_length = sizeof(NEW_VALUE) - 1;
memcpy(new_value, NEW_VALUE, sizeof(NEW_VALUE));
jmp_buf jmp;
if (setjmp(jmp)) {
// If we end up here, that means we wrote at least after_n_bytes to the
// SPI flash. Once we get here, we want to "pretend" that we rebooted,
// and make sure that the contents of the settings_file are not corrupted
// in any way.
// Simulate a reboot by clearing out PFS's state.
fake_spi_flash_force_future_failure(0, NULL);
extern void pfs_reset_all_state(void);
pfs_reset_all_state();
pfs_init(false);
// Reopen the file we were in the middle of writing.
SettingsFile file_new;
cl_must_pass(settings_file_open(&file_new, "test_file_atomic", 4096));
settings_file_dump(&file_new);
int val_len_out = settings_file_get_len(&file_new, key, key_len);
cl_must_pass(val_len_out);
uint8_t *val_out = malloc(val_len_out);
cl_must_pass(settings_file_get(&file_new, key, key_len, val_out, val_len_out));
if (val_len_out == original_value_length) {
printf("original! %d\n", original_value_length);
cl_assert_equal_m(original_value, val_out, val_len_out);
free(val_out);
return RecordResultOld;
} else if (val_len_out == new_value_length) {
printf("new! %d\n", new_value_length);
cl_assert_equal_m(new_value, val_out, val_len_out);
free(val_out);
return RecordResultNew;
}
// Should not get here! This means that neither the old nor the new value
// could be retreived, and thus the atomicity is broken! Aaaaaaaaaaaaaah!!
cl_assert(false);
}
fake_spi_flash_force_future_failure(after_n_bytes, &jmp);
// Setting this key will cause us to jump up to the above if statement if
// we end up writing more than after_n_bytes in order to change the value
// of the key. If we don't write more than after_n_bytes, we will continue
// normally.
set_and_verify(&file_original, key, key_len, new_value, new_value_length);
printf("(never hit limit)\n");
return RecordResultEnd;
}
void test_settings_file__atomic(void) {
printf("\nTesting if we really are atomic...\n");
bool have_hit_old_value = false;
bool have_hit_new_value = false;
bool have_hit_end = false;
for (int i = 1; i < 50; i++) {
printf("Iteration %3d... ", i);
RecordResult result = write_and_change_record_aborting_after_bytes(i);
switch (result) {
case RecordResultOld:
have_hit_old_value = true;
break;
case RecordResultNew:
have_hit_new_value = true;
break;
case RecordResultEnd:
have_hit_end = true;
break;
}
}
cl_assert(have_hit_old_value);
cl_assert(have_hit_new_value);
cl_assert(have_hit_end);
}
void test_settings_file__zero_length(void) {
printf("\nTesting if we can set keys & values of zero length...\n");
SettingsFile file;
cl_must_pass(settings_file_open(&file, "test_file_zero_length", 4096));
uint8_t key[4];
int key_len = sizeof(key) - 1;
memcpy(key, "key", sizeof(key));
uint8_t val[4];
int val_len = sizeof(val) - 1;
memcpy(val, "val", sizeof(val));
set_and_verify(&file, key, key_len, val, val_len);
set_and_verify(&file, key, key_len, val, 0);
// This one's a little funny... We could disallow setting zero-length keys
// because it's unlikely to be something people want, but it currently works
// just fine, and is actually a fairly well-defined operation (after all,
// keys & values are just binary data, so the zero-length key is just as
// unique a key as any other).
set_and_verify(&file, key, 0, val, val_len);
}
static bool prv_each_cb(SettingsFile *file, SettingsRecordInfo *info,
void *context) {
uint8_t *key = malloc(info->key_len + 1);
uint8_t *val = malloc(info->val_len + 1);
info->get_key(file, key, info->key_len);
info->get_val(file, val, info->val_len);
key[info->key_len] = '\0';
val[info->val_len] = '\0';
printf("Read key of %s %d and val of %s %d\n", key, info->key_len, val, info->val_len);
int key_i = atoi((char*)key + 1);
cl_assert(key_i >= 0 && key_i < 255);
uint8_t *counts = (uint8_t*)context;
counts[key_i]++;
status_t result = settings_file_get(file, key, info->key_len, val, info->val_len);
cl_assert_equal_i(result, S_SUCCESS);
int val_i = atoi((char*)val + 1);
cl_assert_equal_i(key_i, val_i);
return true;
}
void test_settings_file__each(void) {
printf("\nTesting if we can use each...\n");
SettingsFile file;
cl_must_pass(settings_file_open(&file, "test_file_each", 4096));
uint8_t key[5];
int key_len = 4;
uint8_t val[5];
int val_len = 4;
for (int i = 0; i < 255; i++) {
snprintf((char *)key, sizeof(key), "k%03d", i);
snprintf((char *)val, sizeof(val), "v%03d", i);
printf("Iteration %d key %s val %s\n", i, key, val);
set_and_verify(&file, key, key_len, val, val_len);
}
uint8_t *counts = task_zalloc(255);
settings_file_each(&file, prv_each_cb, counts);
for (int i = 0; i < 255; i++) {
cl_assert_equal_i(counts[i], 1);
}
task_free(counts);
}
static const uint8_t STOPPING_NUM = 117;
static bool prv_each_cb_quit_early(SettingsFile *file, SettingsRecordInfo *info,
void *context) {
uint8_t *key = malloc(info->key_len + 1);
uint8_t *val = malloc(info->val_len + 1);
info->get_key(file, key, info->key_len);
info->get_val(file, val, info->val_len);
key[info->key_len] = '\0';
val[info->val_len] = '\0';
printf("Read key of %s %d and val of %s %d\n", key, info->key_len, val, info->val_len);
int key_i = atoi((char*)key + 1);
cl_assert(key_i >= 0 && key_i < 255);
uint8_t *cur_val = (uint8_t*)context;
*cur_val = key_i;
int val_i = atoi((char*)val + 1);
cl_assert_equal_i(key_i, val_i);
if (key_i == STOPPING_NUM) {
return false;
}
return true;
}
void test_settings_file__each_quit_early(void) {
printf("\nTesting if we can use each and stop early at %d iterations...\n", STOPPING_NUM);
SettingsFile file;
cl_must_pass(settings_file_open(&file, "test_file_each", 4096));
uint8_t key[5];
int key_len = 4;
uint8_t val[5];
int val_len = 4;
for (int i = 0; i < 255; i++) {
snprintf((char *)key, sizeof(key), "k%03d", i);
snprintf((char *)val, sizeof(val), "v%03d", i);
printf("Iteration %d key %s val %s\n", i, key, val);
set_and_verify(&file, key, key_len, val, val_len);
}
uint8_t cur_val = 0;
settings_file_each(&file, prv_each_cb_quit_early, (void *)&cur_val);
cl_assert_equal_i(STOPPING_NUM, cur_val);
}
void test_settings_file__in_place(void) {
printf("Testing that we can update a setting file in place\n");
SettingsFile file;
cl_must_pass(settings_file_open(&file, "test_file_in_place", 4096));
uint8_t key[4];
int key_len = sizeof(key) - 1;
memcpy(key, "key", sizeof(key));
uint8_t val[4] = {0x11, 0x22, 0x33, 0x44};
int val_len = sizeof(val);
set_and_verify(&file, key, key_len, val, val_len);
// setting a byte to all 0s should work
settings_file_set_byte(&file, key, key_len, 2, 0x00);
val[2] &= 0x00;
verify(&file, key, key_len, val, val_len);
// setting all 1s should do nothing - only reset bytes are applied
settings_file_set_byte(&file, key, key_len, 2, 0xff);
val[2] &= 0xff;
verify(&file, key, key_len, val, val_len);
// setting a mask should leave the other bytes untouched
settings_file_set_byte(&file, key, key_len, 3, 0x40);
val[3] &= 0x40;
verify(&file, key, key_len, val, val_len);
}
// Test that we successfully detect and reallocate a settings file if it gets opened with
// a larger requested size.
void test_settings_file__reallocate_larger(void) {
printf("\nTesting re-allocating a settings file to a larger size\n");
const int k_initial_size = 0x1000;
const int k_larger_size = 0x4000;
SettingsFile file;
cl_must_pass(settings_file_open(&file, "test_file_reallocate_larger", k_initial_size));
int orig_fd_size = pfs_get_file_size(file.iter.fd);
uint8_t key[4];
int key_len = sizeof(key) - 1;
memcpy(key, "key", sizeof(key));
uint8_t val[4];
int val_len = sizeof(val) - 1;
memcpy(val, "val", sizeof(val));
set_and_verify(&file, key, key_len, val, val_len);
// Now close the file and re-open it with a larger size
settings_file_close(&file);
cl_must_pass(settings_file_open(&file, "test_file_reallocate_larger", k_larger_size));
int new_fd_size = pfs_get_file_size(file.iter.fd);
cl_assert(new_fd_size > orig_fd_size);
verify(&file, key, key_len, val, val_len);
}
// Test that we can start searching beginning at the record we previously found in a recent
// call into settings file. This makes sure we don't start searching at the beginning of a file
// each API call.
extern uint32_t settings_raw_iter_prv_get_num_record_searches(void);
void test_settings_file__iterator_wrapping(void) {
printf("Testing that we can call get_len and get without iterator searching again\n");
SettingsFile file;
cl_must_pass(settings_file_open(&file, "test_no_move", 4096));
uint8_t key[5];
int key_len = 4;
uint8_t val[5];
int val_len = 4;
const int NUM_RECORDS = 128;
// First, fill half the file up with random keys and values. This reduces
// our usable space to slightly less than 2048 bytes.
for (int i = 0; i < NUM_RECORDS; i++) {
snprintf((char *)key, sizeof(key), "k%03d", i);
snprintf((char *)val, sizeof(val), "v%03d", i);
set_and_verify(&file, key, key_len, val, val_len);
}
const int search_for_idx = 57;
snprintf((char *)key, sizeof(key), "k%03d", search_for_idx);
// Move iter to the beginning to bring to a known state
settings_raw_iter_begin(&file.iter);
// Get the iter to the record we want
cl_assert_equal_b(true, settings_file_exists(&file, key, key_len));
// Check that we can do a get_len and we don't search past the current record
int before_count = settings_raw_iter_prv_get_num_record_searches();
cl_assert_equal_i(key_len, settings_file_get_len(&file, key, key_len));
int after_count = settings_raw_iter_prv_get_num_record_searches();
cl_assert_equal_i(before_count, after_count);
// Check that we can do a get and we don't search past the current record
before_count = settings_raw_iter_prv_get_num_record_searches();
cl_must_pass(settings_file_get(&file, key, key_len, val, val_len));
after_count = settings_raw_iter_prv_get_num_record_searches();
cl_assert_equal_i(before_count, after_count);
// Force us to move to the record `search_for_idx` + 1
settings_raw_iter_next(&file.iter);
// We now are forced to start searching in the middle, wrap around, and continue searching
// from the beginning. This will result in us calling `settings_raw_iter_next` NUM_RECORDS - 1
// times.
before_count = settings_raw_iter_prv_get_num_record_searches();
cl_must_pass(settings_file_get(&file, key, key_len, val, val_len));
after_count = settings_raw_iter_prv_get_num_record_searches();
cl_assert_equal_i(NUM_RECORDS - 1, after_count - before_count);
}