/* * Copyright 2014 (c) Anna Schumaker. * Test a Database */ #include #include #include "test.h" #include /* * Derive a db_entry for storing integerss */ struct int_entry : public db_entry { unsigned int ie_val; int_entry() : ie_val(0) {}; int_entry(unsigned int v) : ie_val(v) {}; }; static unsigned int test_free_count = 0; static unsigned int test_setup_count = 0; static struct db_entry *int_alloc(const gchar *key) { struct int_entry *ent = new int_entry; sscanf(key, "%u", &ent->ie_val); return ent; } static void int_free(struct db_entry *dbe) { test_free_count++; delete (struct int_entry *)dbe; } static gchar *int_key(struct db_entry *dbe) { struct int_entry *ent = (struct int_entry *)dbe; return g_strdup_printf("%u", ent->ie_val); } static struct db_entry *int_read(struct file *f) { struct int_entry *ent = new int_entry; file_readf(f, "%u", &ent->ie_val); return ent; } static void int_setup(struct db_entry *dbe) { test_setup_count++; } static void int_write(struct file *file, struct db_entry *dbe) { struct int_entry *ent = (struct int_entry *)dbe; file_writef(file, "%u", ent->ie_val); } static const struct db_ops int_ops = { int_alloc, int_free, int_key, int_read, int_setup, int_write, }; static void test_db_entry() { struct int_entry *ent; struct file f; ent = (struct int_entry *)int_ops.dbe_alloc("1"); test_equal(ent->dbe_index, 0); test_equal(ent->ie_val, 1); test_str_equal(int_ops.dbe_key(ent), "1"); file_init(&f, "test_db_entry", 0); file_open(&f, OPEN_WRITE); int_ops.dbe_write(&f, ent); file_close(&f); int_ops.dbe_free(ent); test_equal(test_free_count, 1); file_open(&f, OPEN_READ); ent = (struct int_entry *)int_ops.dbe_read(&f); file_close(&f); int_ops.dbe_setup(ent); test_equal(ent->ie_val, 1); test_str_equal(int_ops.dbe_key(ent), "1"); test_equal(test_setup_count, 1); int_ops.dbe_free(ent); test_equal(test_free_count, 2); } static void test_init() { database db; db_init(&db, "init.db", false, &int_ops); /* Check initial sizes. */ test_equal(db.db_entries.size(), 0); test_equal(db.db_keys.size(), 0); test_equal(db.db_size, 0); test_equal(db.db_autosave, false); test_equal(db.db_file.f_version, 0); test_equal(db.db_file.f_name, "init.db"); db_deinit(&db); } static void test_stress(unsigned int N) { database db; std::vector ptrs; struct int_entry *dbe, *next, rmv(42); unsigned int i; gchar *key; test_free_count = 0; test_setup_count = 0; db_init(&db, "stress.db", false, &int_ops); /* db_insert() */ for (i = 0; i < N; i++) { key = g_strdup_printf("%u", i); dbe = db_insert(&db, new int_entry(i)); test_loop_not_equal(dbe, NULL, i); test_loop_equal(dbe->dbe_index, i, i); test_loop_equal(dbe->dbe_key, key, i); test_loop_equal(dbe->ie_val, i, i); ptrs.push_back(dbe); g_free(key); } test_loop_passed(); dbe = db_insert(&db, (struct int_entry *)NULL); test_equal(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(dbe, NULL, i); test_loop_equal(dbe, ptrs.at(i), i); test_loop_equal(dbe->ie_val, i, i); } test_loop_passed(); test_equal(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(dbe, NULL, i); test_loop_str_equal(int_ops.dbe_key(dbe), key, i); test_loop_equal(dbe, ptrs.at(i), i); test_loop_equal(dbe->ie_val, i, i); g_free(key); } test_loop_passed(); key = g_strdup_printf("%u", N); test_equal(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(dbe, NULL, i); test_loop_str_equal(int_ops.dbe_key(dbe), key, i); test_loop_equal(dbe->ie_val, i, i); if (i < N) test_loop_equal(dbe, ptrs.at(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(db_at(&db, i), NULL, i); test_loop_equal(db_get(&db, key), NULL, i); g_free(key); } test_loop_passed(); db_remove(&db, &rmv); 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(dbe, NULL, i); if (i == (N - 1)) { test_loop_equal(next, NULL, i); } else { test_loop_not_equal(next, NULL, i); test_loop_equal(next->ie_val, i + 2, i); } test_loop_equal(dbe, ptrs.at(i), i); test_loop_equal(dbe->ie_val, i, i); test_loop_equal(db_next(&db, dbe), next, i); i += 2; } test_loop_passed(); test_equal(i, N + 1); db_deinit(&db); test_equal(db.db_size, 0); test_equal(db_actual_size(&db), N + 1); test_equal(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() { database db1, db2; struct int_entry *dbe, *next; const unsigned int N = 10; unsigned int i; db_init(&db1, "save_load.db", true, &int_ops); db_init(&db2, "save_load.db", false, &int_ops); /* 10 items should "autosave" when inserted */ for (i = 0; i < N; i++) db_insert(&db1, new int_entry(i)); 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(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)); db_deinit(&db2); 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(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++) db_insert(&db1, new int_entry(i)); for (i = N; i < (2 * N); i += 2) db_remove(&db1, db_at(&db1, i)); db_deinit(&db2); db_load(&db2); test_equal(db2.db_size, N / 2); db_save(&db1); db_deinit(&db2); db_load(&db2); i = 1; test_equal(db2.db_size, N); test_equal(db_actual_size(&db2), 2 * N); db_for_each(dbe, next, &db2) { test_loop_equal(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), );