335 lines
8.3 KiB
C
335 lines
8.3 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)
|
|
{
|
|
unsigned int val;
|
|
file_readf(f, "%u", &val);
|
|
return &__int_alloc(val)->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 int_entry *ent;
|
|
struct file f;
|
|
|
|
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_init(&f, "test_db_entry", 0);
|
|
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();
|
|
}
|