/* * Copyright (C) 2006-2011 by Benedict Paten (benedictpaten@gmail.com) * * Released under the MIT license, see LICENSE.txt */ /* * kVDatabaseTest.c * */ #include "sonLibGlobalsTest.h" #include "kvDatabaseTestCommon.h" static stKVDatabaseConf *conf = NULL; static stKVDatabase *database = NULL; static void teardown() { if (database != NULL) { stKVDatabase_deleteFromDisk(database); stKVDatabase_destruct(database); database = NULL; } } static void setup() { teardown(); database = stKVDatabase_construct(conf, true); teardown(); database = stKVDatabase_construct(conf, true); } static void constructDestructAndDelete(CuTest *testCase) { setup(); //The setup and teardown functions exercise all the three named functions. teardown(); } static void readWriteAndUpdateIntRecords(CuTest *testCase) { setup(); CuAssertIntEquals(testCase, 0, stKVDatabase_getNumberOfRecords(database)); //Write some int64 records CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, 1)); stKVDatabase_insertInt64(database, -10000, (int64_t)50); stKVDatabase_insertInt64(database, 4297559944418269136, (int64_t)100); stKVDatabase_insertInt64(database, 8457478625225021883, (int64_t)150); CuAssertTrue(testCase, stKVDatabase_getInt64(database, -10000) == 50); CuAssertTrue(testCase, stKVDatabase_getInt64(database, 4297559944418269136) == 100); CuAssertTrue(testCase, stKVDatabase_getInt64(database, 8457478625225021883) == 150); // test update stKVDatabase_insertInt64(database, 4, (int64_t)100); stKVDatabase_updateInt64(database, 4, (int64_t)55); CuAssertTrue(testCase, stKVDatabase_getInt64(database, 4) == 55); //Now try removing a record stKVDatabase_removeRecord(database, 4); CuAssertIntEquals(testCase, 3, stKVDatabase_getNumberOfRecords(database)); CuAssertPtrEquals(testCase, NULL, stKVDatabase_getRecord(database, 0)); CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, 0)); teardown(); } static void readWriteAndRemoveRecords(CuTest *testCase) { setup(); CuAssertIntEquals(testCase, 0, stKVDatabase_getNumberOfRecords(database)); //Write some records CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, -10000)); stKVDatabase_insertRecord(database, -10000, "Red", sizeof(char) * 4); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, -10000)); CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, 4297559944418269136)); stKVDatabase_insertRecord(database, 4297559944418269136, "Green", sizeof(char) * 6); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, 4297559944418269136)); CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, 0)); stKVDatabase_insertRecord(database, 0, "Black", sizeof(char) * 6); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, 0)); //Now read and check the records exist CuAssertIntEquals(testCase, 3, stKVDatabase_getNumberOfRecords(database)); CuAssertStrEquals(testCase, "Red", stKVDatabase_getRecord(database, -10000)); CuAssertStrEquals(testCase, "Green", stKVDatabase_getRecord(database, 4297559944418269136)); CuAssertStrEquals(testCase, "Black", stKVDatabase_getRecord(database, 0)); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, -10000)); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, 4297559944418269136)); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, 0)); //Now check we can retrieve records partially CuAssertStrEquals(testCase, "d", stKVDatabase_getPartialRecord(database, -10000, 2, 2, sizeof(char) * 4)); CuAssertStrEquals(testCase, "ed", stKVDatabase_getPartialRecord(database, -10000, 1, 3, sizeof(char) * 4)); CuAssertStrEquals(testCase, "Red", stKVDatabase_getPartialRecord(database, -10000, 0, 4, sizeof(char) * 4)); char *record = stKVDatabase_getPartialRecord(database, 0, 2, 3, sizeof(char) * 6); record[2] = '\0'; CuAssertStrEquals(testCase, "ac", record); //Now try removing the records stKVDatabase_removeRecord(database, 0); CuAssertIntEquals(testCase, 2, stKVDatabase_getNumberOfRecords(database)); CuAssertPtrEquals(testCase, NULL, stKVDatabase_getRecord(database, 0)); CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, 0)); //Test we get exception if we remove twice. stTry { stKVDatabase_removeRecord(database, 0); CuAssertTrue(testCase, false); } stCatch(except) { CuAssertTrue(testCase, stExcept_getId(except) == ST_KV_DATABASE_EXCEPTION_ID); }stTryEnd; teardown(); } static void partialRecordRetrieval(CuTest *testCase) { setup(); //Make some number of large records stList *records = stList_construct3(0, free); stList *recordSizes = stList_construct3(0, (void(*)(void *)) stIntTuple_destruct); for (int32_t i = 0; i < 300; i++) { int32_t size = st_randomInt(0, 80); size = size * size * size; //Use cubic size distribution char *randomRecord = st_malloc(size * sizeof(char)); for (int32_t j = 0; j < size; j++) { randomRecord[j] = (char) st_randomInt(0, 100); } stList_append(records, randomRecord); stList_append(recordSizes, stIntTuple_construct(1, size)); CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, i)); stKVDatabase_insertRecord(database, i, randomRecord, size * sizeof(char)); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, i)); //st_uglyf("I am creating the record %i %i\n", i, size); } while (st_random() > 0.001) { int32_t recordKey = st_randomInt(0, stList_length(records)); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, recordKey)); char *record = stList_get(records, recordKey); int32_t size = stIntTuple_getPosition(stList_get(recordSizes, recordKey), 0); //Get partial record int32_t start = size > 0 ? st_randomInt(0, size) : 0; int32_t partialSize = size - start > 0 ? st_randomInt(start, size) - start : 0; assert(start >= 0); assert(partialSize >= 0); assert(partialSize + start <= size); //st_uglyf("I am getting record %i %i %i %i\n", recordKey, start, partialSize, size); char *partialRecord = stKVDatabase_getPartialRecord(database, recordKey, start * sizeof(char), partialSize * sizeof(char), size * sizeof(char)); //Check they are equivalent.. for (int32_t i = 0; i < partialSize; i++) { if (record[start + i] != partialRecord[i]) { st_uglyf("There was a difference %i %i for record %i %i\n", record[start + i], partialRecord[i], i, partialSize); } //CuAssertTrue(testCase, record[start + i] == partialRecord[i]); } //Check we can not get out of bounds.. (start less than zero) stTry { stKVDatabase_getPartialRecord(database, recordKey, -1, 1, size * sizeof(char)); }stCatch(except) { CuAssertTrue(testCase, stExcept_getId(except) == ST_KV_DATABASE_EXCEPTION_ID); }stTryEnd; //Check we can not get out of bounds.. (start greater than index start) stTry { stKVDatabase_getPartialRecord(database, recordKey, size, 1, size * sizeof(char)); }stCatch(except) { CuAssertTrue(testCase, stExcept_getId(except) == ST_KV_DATABASE_EXCEPTION_ID); }stTryEnd; //Check we can not get out of bounds.. (total size if greater than record length) stTry { stKVDatabase_getPartialRecord(database, recordKey, 0, size + 1, size * sizeof(char)); }stCatch(except) { CuAssertTrue(testCase, stExcept_getId(except) == ST_KV_DATABASE_EXCEPTION_ID); }stTryEnd; //Check we can not get non existent record stTry { stKVDatabase_getPartialRecord(database, 1000000, 0, size, size * sizeof(char)); }stCatch(except) { CuAssertTrue(testCase, stExcept_getId(except) == ST_KV_DATABASE_EXCEPTION_ID); }stTryEnd; } stList_destruct(records); stList_destruct(recordSizes); teardown(); } static void testIncrementRecord(CuTest *testCase) { /* * Tests incrementing records */ setup(); // note: record we're incrementing must be the same size (8-byte integer) as // the one we're adding to it int64_t i = 100; int64_t key = 7; stKVDatabase_insertInt64(database, key, i); CuAssertTrue(testCase, stKVDatabase_getInt64(database, key) == 100); int64_t l = 10; int64_t m = 15; int64_t n = -25; CuAssertTrue(testCase, stKVDatabase_incrementInt64(database, key, l) == 110); CuAssertTrue(testCase, stKVDatabase_getInt64(database, key) == 110); CuAssertTrue(testCase, stKVDatabase_incrementInt64(database, key, m) == 125); CuAssertTrue(testCase, stKVDatabase_getInt64(database, key) == 125); CuAssertTrue(testCase, stKVDatabase_incrementInt64(database, key, n) == 100); CuAssertTrue(testCase, stKVDatabase_getInt64(database, key) == 100); teardown(); } static void testSetRecord(CuTest *testCase) { /* * Tests 'setting' records (where you don't know if it's an update or an insert). */ setup(); int64_t i = 100; stKVDatabase_setRecord(database, 1, &i, sizeof(int64_t)); int64_t *j = stKVDatabase_getRecord(database, 1); CuAssertTrue(testCase, i == *j); free(j); i = 110; stKVDatabase_setRecord(database, 1, &i, sizeof(int64_t)); j = stKVDatabase_getRecord(database, 1); CuAssertTrue(testCase, i == *j); free(j); teardown(); } static void testBulkSetRecords(CuTest *testCase) { /* * Tests doing a bulk update of a set of records. */ setup(); int64_t i = 100, j = 110, k = 120, l = 130; stKVDatabase_insertRecord(database, 1, &i, sizeof(int64_t)); stList *requests = stList_construct3(0, (void(*)(void *)) stKVDatabaseBulkRequest_destruct); stList_append(requests, stKVDatabaseBulkRequest_constructInsertRequest(2, &j, sizeof(int64_t))); stList_append(requests, stKVDatabaseBulkRequest_constructSetRequest(3, &k, sizeof(int64_t))); stList_append(requests, stKVDatabaseBulkRequest_constructUpdateRequest(1, &l, sizeof(int64_t))); stKVDatabase_bulkSetRecords(database, requests); stList_destruct(requests); int64_t *m = stKVDatabase_getRecord(database, 1); CuAssertTrue(testCase, m != NULL); CuAssertTrue(testCase, l == *m); free(m); m = stKVDatabase_getRecord(database, 2); CuAssertTrue(testCase, m != NULL); CuAssertTrue(testCase, j == *m); free(m); m = stKVDatabase_getRecord(database, 3); CuAssertTrue(testCase, m != NULL); CuAssertTrue(testCase, k == *m); free(m); teardown(); } static void testBulkRemoveRecords(CuTest *testCase) { /* * Tests doing a bulk update of a set of records. */ setup(); int64_t i = 100, j = 110, k = 120, l = 130; stKVDatabase_insertRecord(database, 1, &i, sizeof(int64_t)); stKVDatabase_insertRecord(database, 2, &j, sizeof(int64_t)); stKVDatabase_insertRecord(database, 3, &k, sizeof(int64_t)); stKVDatabase_insertRecord(database, 4, &l, sizeof(int64_t)); stKVDatabase_insertRecord(database, 5, &i, 0); //Test null record addition CuAssertTrue(testCase, stKVDatabase_containsRecord(database, 1)); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, 2)); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, 3)); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, 4)); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, 5)); CuAssertTrue(testCase, stKVDatabase_getNumberOfRecords(database) == 5); stList *requests = stList_construct3(0, (void(*)(void *)) stInt64Tuple_destruct); // test empty request list stKVDatabase_bulkRemoveRecords(database, requests); stList_append(requests, stInt64Tuple_construct(1, (int64_t)1)); stList_append(requests, stInt64Tuple_construct(1, (int64_t)2)); stList_append(requests, stInt64Tuple_construct(1, (int64_t)3)); stList_append(requests, stInt64Tuple_construct(1, (int64_t)4)); stList_append(requests, stInt64Tuple_construct(1, (int64_t)5)); stKVDatabase_bulkRemoveRecords(database, requests); CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, 1)); CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, 2)); CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, 3)); CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, 4)); CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, 5)); CuAssertTrue(testCase, stKVDatabase_getNumberOfRecords(database) == 0); stList_destruct(requests); teardown(); } /* * Retrieves really long records from the database. */ static void bigRecordRetrieval(CuTest *testCase) { setup(); for (int32_t i = 0; i < 10; i++) { int32_t size = st_randomInt(5000000, 10000000); char *randomRecord = st_malloc(size * sizeof(char)); for (int32_t j = 0; j < size; j++) { randomRecord[j] = (char) st_randomInt(0, 100); } //st_uglyf("I am inserting record %i %i\n", i, size); stKVDatabase_insertRecord(database, i, randomRecord, size * sizeof(char)); //st_uglyf("I am creating the record %i %i\n", i, size); //Check they are equivalent. int64_t size2; char *randomRecord2 = stKVDatabase_getRecord2(database, i, &size2); CuAssertTrue(testCase, size == size2); for (int32_t j = 0; j < size; j++) { CuAssertTrue(testCase, randomRecord[j] == randomRecord2[j]); } } teardown(); } /* Check that all tuple records in a set are present and have the expect * value. The expected value in the set is multiplied by valueMult to get * the actual expected value */ static void readWriteAndRemoveRecordsLotsCheck(CuTest *testCase, stSortedSet *set, int valueMult) { CuAssertIntEquals(testCase, stSortedSet_size(set), stKVDatabase_getNumberOfRecords(database)); stSortedSetIterator *it = stSortedSet_getIterator(set); stIntTuple *tuple; while ((tuple = stSortedSet_getNext(it)) != NULL) { int32_t *value = (int32_t *) stKVDatabase_getRecord(database, stIntTuple_getPosition(tuple, 0)); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, stIntTuple_getPosition(tuple, 0))); CuAssertIntEquals(testCase, valueMult*stIntTuple_getPosition(tuple, 0), *value); free(value); } stSortedSet_destructIterator(it); } static void readWriteAndRemoveRecordsLotsIteration(CuTest *testCase, int numRecords, bool reopenDatabase) { //Make a big old list of records.. stSortedSet *set = stSortedSet_construct3((int(*)(const void *, const void *)) stIntTuple_cmpFn, (void(*)(void *)) stIntTuple_destruct); while (stSortedSet_size(set) < numRecords) { int32_t key = st_randomInt(0, 100 * numRecords); stIntTuple *tuple = stIntTuple_construct(1, key); if (stSortedSet_search(set, tuple) == NULL) { CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, key)); stSortedSet_insert(set, tuple); stKVDatabase_insertRecord(database, key, &key, sizeof(int32_t)); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, key)); } else { CuAssertTrue(testCase, stKVDatabase_containsRecord(database, key)); stIntTuple_destruct(tuple); // already in db } } readWriteAndRemoveRecordsLotsCheck(testCase, set, 1); //Update all records to negate values stSortedSetIterator *it = stSortedSet_getIterator(set); stIntTuple *tuple; while ((tuple = stSortedSet_getNext(it)) != NULL) { int32_t *value = (int32_t *) stKVDatabase_getRecord(database, stIntTuple_getPosition(tuple, 0)); *value *= -1; stKVDatabase_updateRecord(database, stIntTuple_getPosition(tuple, 0), value, sizeof(int32_t)); CuAssertTrue(testCase, stKVDatabase_containsRecord(database, stIntTuple_getPosition(tuple, 0))); free(value); } stSortedSet_destructIterator(it); readWriteAndRemoveRecordsLotsCheck(testCase, set, -1); //Try optionally committing the transaction and reloading the database.. if (reopenDatabase) { //stKVDatabase_commitTransaction(database); stKVDatabase_destruct(database); database = stKVDatabase_construct(conf, false); //stKVDatabase_startTransaction(database); } //Now remove each one.. it = stSortedSet_getIterator(set); while ((tuple = stSortedSet_getNext(it)) != NULL) { CuAssertTrue(testCase, stKVDatabase_containsRecord(database, stIntTuple_getPosition(tuple, 0))); stKVDatabase_removeRecord(database, stIntTuple_getPosition(tuple, 0)); CuAssertTrue(testCase, !stKVDatabase_containsRecord(database, stIntTuple_getPosition(tuple, 0))); //Test we get exception if we remove twice. stTry { stKVDatabase_removeRecord(database, stIntTuple_getPosition(tuple, 0)); CuAssertTrue(testCase, 0); } stCatch(except) { CuAssertTrue(testCase, stExcept_getId(except) == ST_KV_DATABASE_EXCEPTION_ID); }stTryEnd; } stSortedSet_destructIterator(it); CuAssertIntEquals(testCase, 0, stKVDatabase_getNumberOfRecords(database)); stSortedSet_destruct(set); } /* * Tests the retrieval of lots of records. */ static void readWriteAndRemoveRecordsLots(CuTest *testCase) { setup(); readWriteAndRemoveRecordsLotsIteration(testCase, 10, false); readWriteAndRemoveRecordsLotsIteration(testCase, 56, true); readWriteAndRemoveRecordsLotsIteration(testCase, 123, false); readWriteAndRemoveRecordsLotsIteration(testCase, 245, true); teardown(); } static void test_stKVDatabaseConf_constructFromString_tokyoCabinet(CuTest *testCase) { const char *xmlTestString = ""; stKVDatabaseConf *conf = stKVDatabaseConf_constructFromString(xmlTestString); CuAssertTrue(testCase, stKVDatabaseConf_getType(conf) == stKVDatabaseTypeTokyoCabinet); CuAssertStrEquals(testCase, "foo", stKVDatabaseConf_getDir(conf)); } static void test_stKVDatabaseConf_constructFromString_mysql(CuTest *testCase) { #ifdef HAVE_MYSQL const char *xmlTestString = ""; stKVDatabaseConf *conf = stKVDatabaseConf_constructFromString( xmlTestString); CuAssertTrue(testCase, stKVDatabaseConf_getType(conf) == stKVDatabaseTypeMySql); CuAssertTrue(testCase, stKVDatabaseConf_getDir(conf) == NULL); CuAssertStrEquals(testCase, "enormous", stKVDatabaseConf_getHost(conf)); CuAssertIntEquals(testCase, 5, stKVDatabaseConf_getPort(conf)); CuAssertStrEquals(testCase, "foo", stKVDatabaseConf_getUser(conf)); CuAssertStrEquals(testCase, "bar", stKVDatabaseConf_getPassword(conf)); CuAssertStrEquals(testCase, "mammals", stKVDatabaseConf_getDatabaseName(conf)); CuAssertStrEquals(testCase, "flowers", stKVDatabaseConf_getTableName(conf)); #endif } static CuSuite* stKVDatabaseTestSuite(void) { CuSuite* suite = CuSuiteNew(); SUITE_ADD_TEST(suite, readWriteAndRemoveRecords); SUITE_ADD_TEST(suite, readWriteAndUpdateIntRecords); SUITE_ADD_TEST(suite, readWriteAndRemoveRecordsLots); SUITE_ADD_TEST(suite, partialRecordRetrieval); SUITE_ADD_TEST(suite, bigRecordRetrieval); SUITE_ADD_TEST(suite, testIncrementRecord); SUITE_ADD_TEST(suite, testSetRecord); SUITE_ADD_TEST(suite, testBulkRemoveRecords); SUITE_ADD_TEST(suite, testBulkSetRecords); SUITE_ADD_TEST(suite, constructDestructAndDelete); SUITE_ADD_TEST(suite, test_stKVDatabaseConf_constructFromString_tokyoCabinet); SUITE_ADD_TEST(suite, test_stKVDatabaseConf_constructFromString_mysql); return suite; } static int runTests(void) { CuString *output = CuStringNew(); CuSuite* suite = CuSuiteNew(); CuSuiteAddSuite(suite, stKVDatabaseTestSuite()); CuSuiteRun(suite); CuSuiteSummary(suite, output); CuSuiteDetails(suite, output); printf("%s\n", output->buffer); return suite->failCount > 0; } int main(int argc, char * const *argv) { const char *desc = "kvDatabaseTests [options]\n" "\n" "Run key/value database tests.\n"; conf = kvDatabaseTestParseOptions(argc, argv, desc, 0, 0, NULL, NULL); return runTests(); }