ocarina/tests/core/database.c
Anna Schumaker e1f13a7ef4 core/file: Create new functions for reading data from files
The readd(), readu(), and readhu() functions are all used to read
various forms of integers.  The readl() and readw() are for reading
either lines or individual whitespace-delimited words

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-21 16:01:15 -05:00

332 lines
8.2 KiB
C

/*
* Copyright 2014 (c) Anna Schumaker.
* Test a Database
*/
#include <core/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 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 index)
{
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 index)
{
return &__int_alloc(file_readu(f))->ie_dbe;
}
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_write = int_write,
};
static void test_db_entry()
{
struct file f = FILE_INIT_DATA("", "test_db_entry", 0);
struct int_entry *ent;
ent = INT_ENTRY(int_ops.dbe_alloc("1", 0));
g_assert_cmpuint(ent->ie_dbe.dbe_index, ==, 0);
g_assert(ent->ie_dbe.dbe_data == ent);
g_assert_cmpuint(ent->ie_val, ==, 1);
g_assert_cmpstr_free(int_ops.dbe_key(&ent->ie_dbe), ==, "1");
file_open(&f, OPEN_WRITE);
int_ops.dbe_write(&f, &ent->ie_dbe);
file_close(&f);
int_ops.dbe_free(&ent->ie_dbe);
g_assert_cmpuint(test_free_count, ==, 1);
file_open(&f, OPEN_READ);
ent = INT_ENTRY(int_ops.dbe_read(&f, 0));
file_close(&f);
g_assert(ent->ie_dbe.dbe_data == ent);
g_assert_cmpuint(ent->ie_val, ==, 1);
g_assert_cmpstr_free(int_ops.dbe_key(&ent->ie_dbe), ==, "1");
int_ops.dbe_free(&ent->ie_dbe);
g_assert_cmpuint(test_free_count, ==, 2);
}
static void test_init()
{
struct database db = DB_INIT("init.db", false, &int_ops, 0);
/* Check initial sizes. */
g_assert_cmpuint(db.db_entries->len, ==, 0);
g_assert_cmpuint(g_hash_table_size(db.db_keys), ==, 0);
g_assert_cmpuint(db.db_size, ==, 0);
g_assert_false(db.db_autosave);
g_assert_cmpuint(db.db_file.f_version, ==, OCARINA_MINOR_VERSION);
g_assert_cmpstr(db.db_file.f_name, ==, "init.db");
db_deinit(&db);
}
static void test_database(gconstpointer arg)
{
unsigned int N = GPOINTER_TO_UINT(arg);
struct database db = DB_INIT("stress.db", false, &int_ops, 0);
struct db_entry *dbe, *next;
struct int_entry rmv;
unsigned int i;
gchar *key;
rmv.ie_val = 42;
rmv.ie_dbe.dbe_index = 0;
test_free_count = 0;
/* db_insert() */
for (i = 0; i < N; i++) {
key = g_strdup_printf("%u", i);
dbe = db_insert(&db, key);
g_assert_nonnull(dbe);
g_assert_cmpuint(dbe->dbe_index, ==, i);
g_assert_cmpstr(dbe->dbe_key, ==, key);
g_assert_cmpuint(INT_ENTRY(dbe)->ie_val, ==, i);
g_free(key);
}
dbe = db_insert(&db, NULL);
g_assert_null(dbe);
g_assert_cmpuint(db.db_size, ==, N);
g_assert_cmpuint(db_actual_size(&db), ==, N);
/* db_at() */
for (i = 0; i < N; i++) {
dbe = db_at(&db, i);
g_assert_nonnull(dbe);
g_assert(dbe == g_ptr_array_index(db.db_entries, i));
g_assert_cmpuint(INT_ENTRY(dbe)->ie_val, ==, i);
}
g_assert_null(db_at(&db, N));
/* db_get() */
for (i = 0; i < N; i++) {
key = g_strdup_printf("%u", i);
dbe = db_get(&db, key);
g_assert_nonnull(dbe);
g_assert_cmpstr_free(int_ops.dbe_key(dbe), ==, key);
g_assert(dbe == db_at(&db, i));
g_assert_cmpuint(INT_ENTRY(dbe)->ie_val, ==, i);
g_free(key);
}
key = g_strdup_printf("%u", N);
g_assert_null(db_get(&db, key));
g_free(key);
/* db_find() */
for (i = 0; i <= N; i++) {
key = g_strdup_printf("%u", i);
dbe = db_find(&db, key);
g_assert_nonnull(dbe);
g_assert_cmpstr_free(int_ops.dbe_key(dbe), ==, key);
g_assert_cmpuint(INT_ENTRY(dbe)->ie_val, ==, i);
if (i < N)
g_assert(dbe == db_at(&db, i));
g_free(key);
}
g_assert_cmpuint(db.db_size, ==, N + 1);
g_assert_cmpuint(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);
g_assert_null(INT_ENTRY(db_at(&db, i)));
g_assert_null(INT_ENTRY(db_get(&db, key)));
g_free(key);
}
db_remove(&db, &rmv.ie_dbe);
g_assert_cmpuint(db.db_size, ==, N / 2);
g_assert_cmpuint(db_actual_size(&db), ==, N + 1);
g_assert_cmpuint(test_free_count, ==, (N / 2) + 1);
/* db_for_each() (db_first() / db_next()) */
i = 1;
db_for_each(dbe, next, &db) {
g_assert_nonnull(dbe);
if (i == (N - 1)) {
g_assert_null(next);
} else {
g_assert_nonnull(next);
g_assert_cmpuint(INT_ENTRY(next)->ie_val, ==, i + 2);
}
g_assert(dbe == db_at(&db, i));
g_assert_cmpuint(INT_ENTRY(dbe)->ie_val, ==, i);
g_assert(db_next(&db, dbe) == next);
i += 2;
}
g_assert_cmpuint(i, ==, N + 1);
/* db_defrag(): Remove all NULL pointers */
i = 0;
g_assert_true(db_defrag(&db));
g_assert_cmpuint(db.db_size, ==, N / 2);
g_assert_cmpuint(db_actual_size(&db), ==, N / 2);
db_for_each(dbe, next, &db) {
g_assert_nonnull(dbe);
g_assert_cmpuint(INT_ENTRY(dbe)->ie_val, ==, (i * 2) + 1);
g_assert(dbe == db_at(&db, i));
i++;
}
g_assert_false(db_defrag(&db));
/* db_rekey() */
i = 0;
db_for_each(dbe, next, &db) {
INT_ENTRY(dbe)->ie_val = i;
db_rekey(&db, dbe);
key = g_strdup_printf("%u", i);
g_assert_cmpstr(dbe->dbe_key, ==, key);
g_assert_true(db_get(&db, key) == dbe);
g_free(key);
i++;
}
db_deinit(&db);
g_assert_cmpuint(db.db_size, ==, 0);
g_assert_cmpuint(db_actual_size(&db), ==, 0);
g_assert_null(db_first(&db));
g_assert_cmpuint(test_free_count, ==, N + 1);
}
static void test_save_load()
{
struct database db1 = DB_INIT("save_load.db", true, &int_ops, 0);
struct database db2 = DB_INIT("save_load.db", false, &int_ops, 0);
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);
g_assert_cmpuint(db2.db_size, ==, N);
g_assert_cmpuint(db_actual_size(&db2), ==, N);
db_for_each(dbe, next, &db2) {
g_assert_cmpuint(INT_ENTRY(dbe)->ie_val, ==, i);
i++;
}
/* 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);
db_load(&db2);
g_assert_cmpuint(db2.db_size, ==, N / 2);
i = 1;
db_for_each(dbe, next, &db2) {
g_assert_cmpuint(INT_ENTRY(dbe)->ie_val, ==, i);
i += 2;
}
/* 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));
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(&db2);
g_assert_cmpuint(db2.db_size, ==, N / 2);
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(&db2);
g_assert_cmpuint(db2.db_size, ==, N);
g_assert_cmpuint(db_actual_size(&db2), ==, 2 * N);
i = 1;
db_for_each(dbe, next, &db2) {
g_assert_cmpuint(INT_ENTRY(dbe)->ie_val, ==, i);
i += 2;
}
db_deinit(&db1);
db_deinit(&db2);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/Core/Database/Entry", test_db_entry);
g_test_add_func("/Core/Database/Initialization", test_init);
g_test_add_data_func("/Core/Database/n = 0", GUINT_TO_POINTER( 0), test_database);
g_test_add_data_func("/Core/Database/n = 10", GUINT_TO_POINTER( 10), test_database);
g_test_add_data_func("/Core/Database/n = 100,000", GUINT_TO_POINTER(100000), test_database);
g_test_add_func("/Core/Database/Save and Load", test_save_load);
return g_test_run();
}