/* * Copyright 2014 (c) Anna Schumaker. * Test a Database */ #include #include #include #include /* * 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; data_file_readf(f, "%u", &val); return &__int_alloc(val)->ie_dbe; } static void int_write(struct file *file, struct db_entry *dbe) { data_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"); data_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); data_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(); }