// The basic set of correctness tests for our database. #include "main.h" #include #include #include #include #include #include #include #include #define PAGE_SIZE 4096 #define BIN_SEARCH 0 #define BTREE_SEARCH 1 #define NO_KEY INT_MIN #define TOMBSTONE INT_MAX #define BITS_BLOOM 5 // testing helper to bulk insert. Inserts consecutive db entries from start_val to end_val. void DB_bulkInsert(DB* db, int start_val, int end_val){ for(int i = start_val; i <= end_val; i++){ db->put(i, i*i); } } // testing helper to print kv_pairs for ease in testing / debugging. void print_KV_pairs(kv_pairs* scanned_kv){ std::cout << "KV pairs of size " << scanned_kv->kv_size << "\n"; for(int i = 0; i < scanned_kv->kv_size; i++){ std::cout << "(" << scanned_kv->kv[i][0] << ", " << scanned_kv->kv[i][1] << ")\n"; } } void test_simple_put_and_get() { // Tests a simple example for put() and get() // Tests if put() and get() works with number of inputs less than max size std::cout << "Running test_simple_put_and_get()\n"; std::filesystem::remove_all("db_t"); DB* t = Open("db_t", 7, 0, 0, BIN_SEARCH, BITS_BLOOM); // AVL Tree with max size 7 t->put(1,11); t->put(5,15); t->put(3,13); t->put(4,14); assert(t->mem_table->max_size == 7); assert(t->mem_table->cur_size == 4); assert(t->get(1) == 11); // Value for key 1 is 11 assert(t->get(5) == 15); // Value for key 5 is 15 assert(t->get(3) == 13); assert(t->get(4) == 14); std::cout << "All tests passed\n\n"; t->Close(); std::filesystem::remove_all("db_t"); } void test_scan_in_range() { // Test that all the pairs properly scan if range includes them all and in order std::cout << "Running test_scan_in_range()\n"; std::filesystem::remove_all("db_t"); DB* t = Open("db_t", 7, 0, 0, BIN_SEARCH, BITS_BLOOM); // AVL Tree with max size 7 t->put(1,11); t->put(5,15); t->put(3,13); t->put(4,14); struct kv_pairs* scanned_kv = t->scan(-100, 100); int expected[4][2] = { {1, 11}, {3, 13}, {4, 14}, {5, 15} }; // Test that the KV pairs that were scanned match what we expect assert(scanned_kv->kv_size == 4); for (int i = 0; i < scanned_kv->kv_size; i++) { assert(scanned_kv->kv[i][0] == expected[i][0]); assert(scanned_kv->kv[i][1] == expected[i][1]); } std::cout << "All tests passed\n\n"; t->freeKVpairs(scanned_kv); t->Close(); std::filesystem::remove_all("db_t"); } void test_scan_partial_range() { std::cout << "Running test_scan_partial_range()\n"; std::filesystem::remove_all("db_t"); DB* t = Open("db_t", 7, 0, 0, BIN_SEARCH, BITS_BLOOM); // AVL Tree with max size 7 t->put(1,11); t->put(5,15); t->put(3,13); t->put(4,14); int expected[2][2] = { {3, 13}, {4, 14} }; struct kv_pairs* scanned_kv = t->scan(3, 4); // Test that the KV pairs that were scanned match what we expect for (int i = 0; i < scanned_kv->kv_size; i++) { assert(scanned_kv->kv[i][0] == expected[i][0]); assert(scanned_kv->kv[i][1] == expected[i][1]); } std::cout << "All tests passed\n\n"; t->freeKVpairs(scanned_kv); t->Close(); std::filesystem::remove_all("db_t"); } void test_transform_sst() { // Tests that when the tree is full, we transform it to an SST and create a new tree. std::cout << "Running test_transform_sst()\n"; std::filesystem::remove_all("db_t"); DB* t = Open("db_t", 3, 4, 4, BIN_SEARCH, BITS_BLOOM); // AVL Tree with max size 3 t->put(1,11); t->put(5,15); t->put(3,13); t->put(2,12); t->put(4,16); t->put(7,19); struct kv_pairs* scanned_kv = t->scan(0, 9); // Test that the sizes are correct assert(scanned_kv->kv_size == 6); assert(t->mem_table->cur_size == 3); // Test that the KV pairs that were scanned match what we expect int expected[6][2] = { {1, 11}, {2, 12}, {3, 13}, {4, 16}, {5, 15}, {7, 19} }; for (int i = 0; i < scanned_kv->kv_size; i++) { assert(scanned_kv->kv[i][0] == expected[i][0]); assert(scanned_kv->kv[i][1] == expected[i][1]); } std::cout << "All tests passed\n\n"; t->freeKVpairs(scanned_kv); t->Close(); std::filesystem::remove_all("db_t"); } void test_transform_sst_btree() { // Tests that when the tree is full, we transform it to an SST and create a new tree. std::cout << "Running test_transform_sst_btree()\n"; std::filesystem::remove_all("db_t"); DB* t = Open("db_t", 3, 4, 4, BTREE_SEARCH, BITS_BLOOM); // AVL Tree with max size 3 t->put(1,11); t->put(5,15); t->put(3,13); t->put(2,12); t->put(4,16); t->put(7,19); struct kv_pairs* scanned_kv = t->scan(0, 9); // Test that the sizes are correct assert(scanned_kv->kv_size == 6); assert(t->mem_table->cur_size == 3); // Test that the KV pairs that were scanned match what we expect int expected[6][2] = { {1, 11}, {2, 12}, {3, 13}, {4, 16}, {5, 15}, {7, 19} }; for (int i = 0; i < scanned_kv->kv_size; i++) { assert(scanned_kv->kv[i][0] == expected[i][0]); assert(scanned_kv->kv[i][1] == expected[i][1]); } std::cout << "All tests passed\n\n"; t->freeKVpairs(scanned_kv); t->Close(); std::filesystem::remove_all("db_t"); } void test_invalid_get() { // Test that when we call get on an invalid key, we get NO_KEY std::cout << "Running test_invalid_get()\n"; DB* t = Open("db_t", 3, 0, 0, BIN_SEARCH, BITS_BLOOM); // AVL Tree with max size 3 t->put(1,11); t->put(5,15); t->put(3,13); assert(t->get(8) == NO_KEY); std::cout << "All tests passed\n\n"; t->Close(); std::filesystem::remove_all("db_t"); } void test_large_scan_sst() { // The below tests test that the tree is correctly moving to SST and that a new tree is reallocated each time. std::cout << "Running test_large_scan_sst()\n"; DB* t = Open("db_t", 12, 4, 4, BIN_SEARCH, BITS_BLOOM); // AVL Tree with max size 12 int expected[134][2]; int ex_idx = 0; for(int i = 0; i < 100; i++){ if(i % 3 == 0) { t->put(100 + i, 100 + i); } t->put(i, i*i); expected[ex_idx][0] = i; expected[ex_idx][1] = i * i; ex_idx += 1; } for(int i = 0; i < 100; i++) { // Another loop so that expected is in sorted order if (i % 3 == 0) { expected[ex_idx][0] = 100 + i; expected[ex_idx][1] = 100 + i; ex_idx += 1; } } std::cout << "Waiting to scan...\n"; struct kv_pairs* scanned_kv = t->scan(0, 300); //std::cout << "Scan size: " << scanned_kv->kv_size << "\n"; //print_KV_pairs(scanned_kv); assert(scanned_kv->kv_size == 134); for (int i = 0; i < scanned_kv->kv_size; i++) { assert(scanned_kv->kv[i][0] == expected[i][0]); assert(scanned_kv->kv[i][1] == expected[i][1]); } std::cout << "All tests passed\n\n"; t->freeKVpairs(scanned_kv); t->Close(); std::filesystem::remove_all("db_t"); } void test_large_scan_sst_btree() { // The below tests test that the tree is correctly moving to SST and that a new tree is reallocated each time. std::cout << "Running test_large_scan_sst_btree()\n"; DB* t = Open("db_t", 12, 4, 4, BTREE_SEARCH, BITS_BLOOM); // AVL Tree with max size 12 int expected[134][2]; int ex_idx = 0; for(int i = 0; i < 100; i++){ if(i % 3 == 0) { t->put(100 + i, 100 + i); } t->put(i, i*i); expected[ex_idx][0] = i; expected[ex_idx][1] = i * i; ex_idx += 1; } for(int i = 0; i < 100; i++) { // Another loop so that expected is in sorted order if (i % 3 == 0) { expected[ex_idx][0] = 100 + i; expected[ex_idx][1] = 100 + i; ex_idx += 1; } } std::cout << "Waiting to scan...\n"; struct kv_pairs* scanned_kv = t->scan(0, 300); assert(scanned_kv->kv_size == 134); for (int i = 0; i < scanned_kv->kv_size; i++) { assert(scanned_kv->kv[i][0] == expected[i][0]); assert(scanned_kv->kv[i][1] == expected[i][1]); } std::cout << "All tests passed\n\n"; t->freeKVpairs(scanned_kv); t->Close(); std::filesystem::remove_all("db_t"); } void test_open_close_same_db() { // Tests that open and close work, and that after closing a DB, opening // it at a later time will retain the data stored early in the SSTs std::cout << "Running test_open_close_same_db()\n"; DB* db = Open("db_t", 4, 0, 0, BIN_SEARCH, BITS_BLOOM); db->put(1, 11); db->put(2, 12); db->put(3, 13); db->put(4, 14); assert(db->mem_table->max_size == 4); assert(db->mem_table->cur_size == 4); assert(db->get(1) == 11); assert(db->get(5) == NO_KEY); // doesn't exist assert(db->get(3) == 13); db->put(7,17); db->put(8,18); db->Close(); DB* db2 = Open("db_t", 4, 0, 0, BIN_SEARCH, BITS_BLOOM); // the same one as above assert(db2->get(1) == 11); assert(db2->get(8) == 18); db2->put(5, 15); struct kv_pairs* scanned_kv = db2->scan(3,8); std::cout << scanned_kv->kv_size << " size\n"; print_KV_pairs(scanned_kv); int expected[5][2] = { {3, 13}, {4, 14}, {5, 15}, {7, 17}, {8, 18} }; assert(scanned_kv->kv_size == 5); for (int i = 0; i < scanned_kv->kv_size; i++) { assert(scanned_kv->kv[i][0] == expected[i][0]); assert(scanned_kv->kv[i][1] == expected[i][1]); } db2->freeKVpairs(scanned_kv); db2->Close(); std::cout << "All tests passed\n\n"; std::filesystem::remove_all("db_t"); } void test_open_close_same_db_btree() { // Tests that open and close work, and that after closing a DB, opening // it at a later time will retain the data stored early in the SSTs std::cout << "Running test_open_close_same_db_btree()\n"; DB* db = Open("db_t", 4, 0, 0, BTREE_SEARCH, BITS_BLOOM); db->put(1, 11); db->put(2, 12); db->put(3, 13); db->put(4, 14); assert(db->mem_table->max_size == 4); assert(db->mem_table->cur_size == 4); assert(db->get(1) == 11); assert(db->get(5) == NO_KEY); // doesn't exist assert(db->get(3) == 13); db->put(7,17); db->put(8,18); db->Close(); DB* db2 = Open("db_t", 4, 0, 0, BIN_SEARCH, BITS_BLOOM); // the same one as above assert(db2->get(1) == 11); assert(db2->get(8) == 18); db2->put(5, 15); struct kv_pairs* scanned_kv = db2->scan(3,8); int expected[5][2] = { {3, 13}, {4, 14}, {5, 15}, {7, 17}, {8, 18} }; assert(scanned_kv->kv_size == 5); for (int i = 0; i < scanned_kv->kv_size; i++) { assert(scanned_kv->kv[i][0] == expected[i][0]); assert(scanned_kv->kv[i][1] == expected[i][1]); } db2->freeKVpairs(scanned_kv); db2->Close(); std::cout << "All tests passed\n\n"; std::filesystem::remove_all("db_t"); } void test_update(){ // test that updates are properly handled by get() and scan(). std::cout << "Running test_update()\n"; DB* db = Open("db_t", 8, 0, 0, BTREE_SEARCH, BITS_BLOOM); DB_bulkInsert(db, 1, 30); // insert 30 entries db->update(1, 0); db->update(3, 0); db->update(22, 0); DB_bulkInsert(db, 31, 60); // insert 30 entries db->update(45, 0); db->update(37, 0); db->update(15, 0); DB_bulkInsert(db, 61, 90); // insert 30 entries db->update(77, 0); db->update(55, 0); db->update(17, 0); db->update(16, 0); struct kv_pairs* scanned_kv = db->scan(0,100); assert(scanned_kv->kv_size == 90); // print_KV_pairs(scanned_kv); // Optional to confirm that the right elements are printed. db->freeKVpairs(scanned_kv); db->Close(); std::cout << "All tests passed\n\n"; std::filesystem::remove_all("db_t"); } void test_remove_get(){ // Tests that deletes work in memtable and in SSTs with get(). std::cout << "Running test_remove_get()\n"; DB* db = Open("db_t", 4, 0, 0, BIN_SEARCH, BITS_BLOOM); db->put(1, 11); db->put(2, 12); db->put(3, 13); assert(db->get(2) == 12); // 2 in in table db->remove(2); assert(db->get(2) == NO_KEY); // 2 doesn't exist (tombstone works) DB_bulkInsert(db, 10, 19); // insert 10 entries, moves 3 out of memtable assert(db->get(3) == 13); // 3 in SST db->remove(3); assert(db->get(3) == NO_KEY); // 3 doesn't exist (tombstone works on memtable) db->put(2, 22); // put 2 in the DB again (on a different SST than tombstone) DB_bulkInsert(db, 20, 29); // insert 10 entries again (2 is flushed into the tombstone) assert(db->get(2) == 22); // most recent entry is read over the tombstone db->Close(); std::cout << "All tests passed\n\n"; std::filesystem::remove_all("db_t"); } void test_remove_scan(){ // test that tombstones are properly excluded from scans, but also keep removed elements out. std::cout << "Running test_remove_scan()\n"; DB* db = Open("db_t", 8, 0, 0, BTREE_SEARCH, BITS_BLOOM); DB_bulkInsert(db, 1, 30); // insert 30 entries db->remove(2); db->remove(7); db->remove(13); db->remove(21); db->remove(27); DB_bulkInsert(db, 31, 60); // insert 30 entries db->remove(5); db->remove(59); struct kv_pairs* scanned_kv = db->scan(0,100); // print_KV_pairs(scanned_kv); assert(scanned_kv->kv_size == 60 - 7); //print_KV_pairs(scanned_kv); Optional to confirm that the right elements are printed. db->freeKVpairs(scanned_kv); db->Close(); std::cout << "All tests passed\n\n"; std::filesystem::remove_all("db_t"); } void test_bloom_filter(){ std::cout << "Running test_bloom_filter()\n"; DB* db = Open("db_t", 512, 8, 32, BTREE_SEARCH, BITS_BLOOM); for (int i = 0; i < 1024; i++){ db->put(i, i+1); } assert(db->get(1) == 2); assert(db->get(100) == 101); assert(db->get(1000) == 1001); // only sst0 with first 512 keys bool exist; for (int j = 0; j < 512; j++) { exist = db->check_bloom_filters(0, j); assert(exist); // all inserted numbers must exist } int fp_count = 0; for (int k = 800; k < 1800; k++){ bool exist = db->check_bloom_filters(0, k); if (exist){ fp_count++; } } std::cout << "False Positive Rate: " << (fp_count/10.0) << "% \n"; db->Close(); DB* db2 = Open("db_t", 512, 8, 32, BTREE_SEARCH, BITS_BLOOM); // all 1024 entries are in, merged into sst1, level is 1 for (int n = 0; n < 1024; n++) { exist = db2->check_bloom_filters(1, n); if (!exist) { std::cout << "Not found: "<< n << "\n"; } assert(exist); // all inserted numbers must exist } int fp_count2 = 0; for (int m = 2000; m < 12000; m++){ bool exist = db2->check_bloom_filters(1, m); if (exist){ fp_count2++; } } db2->set_max_dir_size(16); assert(db2->get(1) == 2); assert(db2->get(100) == 101); assert(db2->get(1000) == 1001); std::cout << "False Positive Rate: " << (fp_count2/100.0) << "% \n"; db2->Close(); std::cout << "All tests passed\n\n"; std::filesystem::remove_all("db_t"); } void test_compaction() { std::filesystem::remove_all("db_t"); std::cout << "Running test_compaction()\n"; DB* db = Open("db_t", 512, 0, 0, BTREE_SEARCH, BITS_BLOOM); // Insert enough elements to force two memtable flushes, leading to compaction of levle 0 SSTs making a level 1 SST for (int i = 0; i < 1536; i++){ db->put(i, i+1); } // assert we can scan every element that is outside of the memtable, i.e. in level 1 SST struct kv_pairs* scanned_kv = db->scan(0, 1025); assert (scanned_kv->kv_size = 1026); // Ensure we can get all 1536 elements, i.e. level 1 sst is correctly formed for (int i = 0; i < 1536; i++){ db->get(i); } assert(db->get(1) == 2); assert(db->get(100) == 101); assert(db->get(1000) == 1001); db->Close(); std::filesystem::remove_all("db_t"); } int main() { std::cout << "RUNNING TESTS\n\n"; std::filesystem::remove_all("db_t"); test_simple_put_and_get(); test_scan_in_range(); test_scan_partial_range(); test_transform_sst(); test_transform_sst_btree(); test_invalid_get(); test_large_scan_sst(); test_large_scan_sst_btree(); test_open_close_same_db(); test_open_close_same_db_btree(); test_remove_get(); test_remove_scan(); test_update(); test_bloom_filter(); test_compaction(); std::filesystem::remove_all("db_t"); }