ocarina/tests/core/containers/database.c

334 lines
8.4 KiB
C

/*
* Copyright 2014 (c) Anna Schumaker.
* Test a Database
*/
#include <core/containers/database.h>
#include <core/idle.h>
#include <core/string.h>
#include <tests/test.h>
/*
* Derive a db_entry for storing integerss
*/
struct int_entry {
unsigned int ie_val;
struct db_entry ie_dbe;
};
#define INT_ENTRY(dbe) ((struct int_entry *)DBE_DATA(dbe))
static unsigned int test_free_count = 0;
static unsigned int test_setup_count = 0;
static struct int_entry *__int_alloc(unsigned int val)
{
struct int_entry *ent = g_malloc(sizeof(struct int_entry));
ent->ie_val = val;
dbe_init(&ent->ie_dbe, ent);
return ent;
}
static struct db_entry *int_alloc(const gchar *key)
{
unsigned int val;
sscanf(key, "%u", &val);
return &__int_alloc(val)->ie_dbe;
}
static void int_free(struct db_entry *dbe)
{
test_free_count++;
g_free(INT_ENTRY(dbe));
}
static gchar *int_key(struct db_entry *dbe)
{
return g_strdup_printf("%u", INT_ENTRY(dbe)->ie_val);
}
static struct db_entry *int_read(struct file *f)
{
unsigned int val;
file_readf(f, "%u", &val);
return &__int_alloc(val)->ie_dbe;
}
static void int_setup(struct db_entry *dbe)
{
test_setup_count++;
}
static void int_write(struct file *file, struct db_entry *dbe)
{
file_writef(file, "%u", INT_ENTRY(dbe)->ie_val);
}
static const struct db_ops int_ops = {
.dbe_alloc = int_alloc,
.dbe_free = int_free,
.dbe_key = int_key,
.dbe_read = int_read,
.dbe_setup = int_setup,
.dbe_write = int_write,
};
static void test_db_entry()
{
struct int_entry *ent;
struct file f;
ent = INT_ENTRY(int_ops.dbe_alloc("1"));
test_equal(ent->ie_dbe.dbe_index, 0);
test_equal((void *)ent->ie_dbe.dbe_data, (void *)ent);
test_equal(ent->ie_val, 1);
test_str_equal(int_ops.dbe_key(&ent->ie_dbe), "1");
file_init(&f, "test_db_entry", 0, 0);
file_open(&f, OPEN_WRITE);
int_ops.dbe_write(&f, &ent->ie_dbe);
file_close(&f);
int_ops.dbe_free(&ent->ie_dbe);
test_equal(test_free_count, 1);
file_open(&f, OPEN_READ);
ent = INT_ENTRY(int_ops.dbe_read(&f));
file_close(&f);
int_ops.dbe_setup(&ent->ie_dbe);
test_equal((void *)ent->ie_dbe.dbe_data, (void *)ent);
test_equal(ent->ie_val, 1);
test_str_equal(int_ops.dbe_key(&ent->ie_dbe), "1");
test_equal(test_setup_count, 1);
int_ops.dbe_free(&ent->ie_dbe);
test_equal(test_free_count, 2);
}
static void test_init()
{
struct database db = DB_INIT("init.db", false, &int_ops);
/* Check initial sizes. */
test_equal(db.db_entries->len, 0);
test_equal(g_hash_table_size(db.db_keys), 0);
test_equal(db.db_size, 0);
test_equal(db.db_autosave, (bool)false);
test_equal(db.db_file.f_version, 0);
test_equal(db.db_file.f_name, "init.db");
db_deinit(&db);
}
#define PTRS_AT(db, index) \
g_ptr_array_index(db, index)
static void test_stress(unsigned int N)
{
struct database db = DB_INIT("stress.db", false, &int_ops);;
struct db_entry *dbe, *next;
struct int_entry rmv;
GPtrArray *ptrs;
unsigned int i;
gchar *key;
rmv.ie_val = 42;
rmv.ie_dbe.dbe_index = 0;
test_free_count = 0;
test_setup_count = 0;
ptrs = g_ptr_array_new();
/* db_insert() */
for (i = 0; i < N; i++) {
key = g_strdup_printf("%u", i);
dbe = db_insert(&db, key);
test_loop_not_equal((void *)dbe, NULL, i);
test_loop_equal(dbe->dbe_index, i, i);
test_loop_equal(dbe->dbe_key, key, i);
test_loop_equal(INT_ENTRY(dbe)->ie_val, i, i);
g_ptr_array_add(ptrs, INT_ENTRY(dbe));
g_free(key);
} test_loop_passed();
dbe = db_insert(&db, NULL);
test_equal((void *)dbe, NULL);
test_equal(db.db_size, N);
test_equal(db_actual_size(&db), N);
test_equal(test_setup_count, N);
/* db_at() */
for (i = 0; i < N; i++) {
dbe = db_at(&db, i);
test_loop_not_equal((void *)dbe, NULL, i);
test_loop_equal((void *)INT_ENTRY(dbe), PTRS_AT(ptrs, i), i);
test_loop_equal(INT_ENTRY(dbe)->ie_val, i, i);
} test_loop_passed();
test_equal((void *)db_at(&db, N), NULL);
/* db_get() */
for (i = 0; i < N; i++) {
key = g_strdup_printf("%u", i);
dbe = db_get(&db, key);
test_loop_not_equal((void *)dbe, NULL, i);
test_loop_str_equal(int_ops.dbe_key(dbe), key, i);
test_loop_equal((void *)INT_ENTRY(dbe), PTRS_AT(ptrs, i), i);
test_loop_equal(INT_ENTRY(dbe)->ie_val, i, i);
g_free(key);
} test_loop_passed();
key = g_strdup_printf("%u", N);
test_equal((void *)db_get(&db, key), NULL);
g_free(key);
/* db_find() */
for (i = 0; i <= N; i++) {
key = g_strdup_printf("%u", i);
dbe = db_find(&db, key);
test_loop_not_equal((void *)dbe, NULL, i);
test_loop_str_equal(int_ops.dbe_key(dbe), key, i);
test_loop_equal(INT_ENTRY(dbe)->ie_val, i, i);
if (i < N)
test_loop_equal((void *)INT_ENTRY(dbe), PTRS_AT(ptrs, i), i);
g_free(key);
} test_loop_passed();
test_equal(db.db_size, N + 1);
test_equal(db_actual_size(&db), N + 1);
/* db_remove(): Even indices only! */
for (i = 0; i <= (N + 2); i += 2) {
key = g_strdup_printf("%u", i);
dbe = db_get(&db, key);
db_remove(&db, dbe);
test_loop_equal((void *)INT_ENTRY(db_at(&db, i)), NULL, i);
test_loop_equal((void *)INT_ENTRY(db_get(&db, key)), NULL, i);
g_free(key);
} test_loop_passed();
db_remove(&db, &rmv.ie_dbe);
test_equal(db.db_size, N / 2);
test_equal(db_actual_size(&db), N + 1);
test_equal(test_free_count, (N / 2) + 1);
/* db_for_each() (db_first() / db_next()) */
i = 1;
db_for_each(dbe, next, &db) {
test_loop_not_equal((void *)dbe, NULL, i);
if (i == (N - 1)) {
test_loop_equal((void *)next, NULL, i);
} else {
test_loop_not_equal((void *)next, NULL, i);
test_loop_equal(INT_ENTRY(next)->ie_val, i + 2, i);
}
test_loop_equal((void *)INT_ENTRY(dbe), PTRS_AT(ptrs, i), i);
test_loop_equal(INT_ENTRY(dbe)->ie_val, i, i);
test_loop_equal((void *)db_next(&db, dbe), (void *)next, i);
i += 2;
} test_loop_passed();
test_equal(i, N + 1);
db_deinit(&db);
g_ptr_array_free(ptrs, true);
test_equal(db.db_size, 0);
test_equal(db_actual_size(&db), 0);
test_equal((void *)db_first(&db), NULL);
test_equal(test_free_count, N + 1);
}
static void test_basics() { test_stress(10); }
static void test_stress_0() { test_stress(0); }
static void test_stress_100K() { test_stress(100000); }
static void test_save_load()
{
struct database db1 = DB_INIT("save_load.db", true, &int_ops);
struct database db2 = DB_INIT("save_load.db", false, &int_ops);
struct db_entry *dbe, *next;
const unsigned int N = 10;
unsigned int i;
gchar *key;
/* 10 items should "autosave" when inserted */
for (i = 0; i < N; i++) {
key = g_strdup_printf("%u", i);
db_insert(&db1, key);
g_free(key);
}
/* Load using the standard db_load() function */
i = 0;
db_load(&db2);
test_equal(db2.db_size, N);
test_equal(db_actual_size(&db2), N);
db_for_each(dbe, next, &db2) {
test_loop_equal(INT_ENTRY(dbe)->ie_val, i, i);
i++;
} test_loop_passed();
/* Removing 5 items, should also trigger autosaving. */
for (i = 0; i < N; i += 2)
db_remove(&db1, db_at(&db1, i));
/* Use db_load() again */
db_deinit(&db2);
db2.db_entries = g_ptr_array_new();
db2.db_keys = g_hash_table_new(g_str_hash, g_str_equal);
test_setup_count = 0;
db_load(&db2);
test_equal(db2.db_size, N / 2);
test_equal(test_setup_count, N / 2);
i = 1;
db_for_each(dbe, next, &db2) {
test_loop_equal(INT_ENTRY(dbe)->ie_val, i, i);
i += 2;
} test_loop_passed();
/* Manually turn autosave off. */
db1.db_autosave = false;
for (i = N; i < (2 * N); i++) {
key = g_strdup_printf("%u", i);
db_insert(&db1, key);
g_free(key);
}
for (i = N; i < (2 * N); i += 2)
db_remove(&db1, db_at(&db1, i));
/* Use db_load_idle() this time */
db_deinit(&db2);
db2.db_entries = g_ptr_array_new();
db2.db_keys = g_hash_table_new(g_str_hash, g_str_equal);
db_load_idle(&db2);
test_equal(db2.db_size, 0);
while (idle_run_task()) {};
test_equal(db2.db_size, N / 2);
/* Use db_load_idle() again */
db_save(&db1);
db_deinit(&db2);
db2.db_entries = g_ptr_array_new();
db2.db_keys = g_hash_table_new(g_str_hash, g_str_equal);
db_load_idle(&db2);
test_equal(db2.db_size, 0);
while (idle_run_task()) {};
test_equal(db2.db_size, N);
test_equal(db_actual_size(&db2), 2 * N);
i = 1;
db_for_each(dbe, next, &db2) {
test_loop_equal(INT_ENTRY(dbe)->ie_val, i, i);
i += 2;
} test_loop_passed();
db_deinit(&db1);
db_deinit(&db2);
}
DECLARE_UNIT_TESTS(
UNIT_TEST("Database Entry", test_db_entry),
UNIT_TEST("Database Initialization", test_init),
UNIT_TEST("Database Basics", test_basics),
UNIT_TEST("Database Stress (n = 0)", test_stress_0),
UNIT_TEST("Database Stress (n = 100,000)", test_stress_100K),
UNIT_TEST("Database Save and Load", test_save_load),
);