/** * GdbmDbmTest - Unit tests for GdbmDbm storage */ using Implexus.Storage; public static int main(string[] args) { int passed = 0; int failed = 0; // Test 1: Basic set and get if (test_basic_set_get()) { passed++; stdout.puts("PASS: test_basic_set_get\n"); } else { failed++; stdout.puts("FAIL: test_basic_set_get\n"); } // Test 2: Get non-existent key if (test_get_nonexistent()) { passed++; stdout.puts("PASS: test_get_nonexistent\n"); } else { failed++; stdout.puts("FAIL: test_get_nonexistent\n"); } // Test 3: Delete key if (test_delete_key()) { passed++; stdout.puts("PASS: test_delete_key\n"); } else { failed++; stdout.puts("FAIL: test_delete_key\n"); } // Test 4: Has key if (test_has_key()) { passed++; stdout.puts("PASS: test_has_key\n"); } else { failed++; stdout.puts("FAIL: test_has_key\n"); } // Test 5: Keys iteration if (test_keys_iteration()) { passed++; stdout.puts("PASS: test_keys_iteration\n"); } else { failed++; stdout.puts("FAIL: test_keys_iteration\n"); } // Test 6: Transaction commit if (test_transaction_commit()) { passed++; stdout.puts("PASS: test_transaction_commit\n"); } else { failed++; stdout.puts("FAIL: test_transaction_commit\n"); } // Test 7: Transaction rollback if (test_transaction_rollback()) { passed++; stdout.puts("PASS: test_transaction_rollback\n"); } else { failed++; stdout.puts("FAIL: test_transaction_rollback\n"); } // Test 8: Transaction with delete if (test_transaction_delete()) { passed++; stdout.puts("PASS: test_transaction_delete\n"); } else { failed++; stdout.puts("FAIL: test_transaction_delete\n"); } // Test 9: Overwrite value if (test_overwrite_value()) { passed++; stdout.puts("PASS: test_overwrite_value\n"); } else { failed++; stdout.puts("FAIL: test_overwrite_value\n"); } // Test 10: Special key names if (test_special_key_names()) { passed++; stdout.puts("PASS: test_special_key_names\n"); } else { failed++; stdout.puts("FAIL: test_special_key_names\n"); } // Test 11: Binary data if (test_binary_data()) { passed++; stdout.puts("PASS: test_binary_data\n"); } else { failed++; stdout.puts("FAIL: test_binary_data\n"); } // Test 12: Empty key if (test_empty_key()) { passed++; stdout.puts("PASS: test_empty_key\n"); } else { failed++; stdout.puts("FAIL: test_empty_key\n"); } // Test 13: Large value if (test_large_value()) { passed++; stdout.puts("PASS: test_large_value\n"); } else { failed++; stdout.puts("FAIL: test_large_value\n"); } // Test 14: Multiple operations if (test_multiple_operations()) { passed++; stdout.puts("PASS: test_multiple_operations\n"); } else { failed++; stdout.puts("FAIL: test_multiple_operations\n"); } // Test 15: Persistence if (test_persistence()) { passed++; stdout.puts("PASS: test_persistence\n"); } else { failed++; stdout.puts("FAIL: test_persistence\n"); } // Test 16: Open and close if (test_open_close()) { passed++; stdout.puts("PASS: test_open_close\n"); } else { failed++; stdout.puts("FAIL: test_open_close\n"); } stdout.printf("\nResults: %d passed, %d failed\n", passed, failed); return failed > 0 ? 1 : 0; } // Helper to create temporary database path string create_temp_db_path() { string temp_dir = DirUtils.mkdtemp("implexus_gdbm_test_XXXXXX"); return Path.build_filename(temp_dir, "test.db"); } // Helper to get directory from db path string get_dir_from_path(string db_path) { return Path.get_dirname(db_path); } // Helper to create BinaryData from string (includes null terminator) Invercargill.BinaryData string_to_binary(string str) { // Include null terminator for proper string reconstruction when reading back uint8[] data = new uint8[str.length + 1]; Memory.copy(data, str, str.length); data[str.length] = 0; // null terminator return new Invercargill.DataStructures.ByteBuffer.from_byte_array(data); } // Helper to convert BinaryData to string string binary_to_string(Invercargill.BinaryData data) { var bytes = data.to_bytes(); return (string) bytes.get_data(); } // Test 1: Basic set and get bool test_basic_set_get() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); dbm.set("test_key", string_to_binary("test_value")); var value = dbm.get("test_key"); if (value == null) { dbm.close(); cleanup_dir(temp_dir); return false; } if (binary_to_string((!) value) != "test_value") { dbm.close(); cleanup_dir(temp_dir); return false; } dbm.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 2: Get non-existent key bool test_get_nonexistent() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); var value = dbm.get("nonexistent"); dbm.close(); cleanup_dir(temp_dir); return value == null; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 3: Delete key bool test_delete_key() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); dbm.set("key1", string_to_binary("value1")); if (!dbm.has_key("key1")) { dbm.close(); cleanup_dir(temp_dir); return false; } dbm.delete("key1"); if (dbm.has_key("key1")) { dbm.close(); cleanup_dir(temp_dir); return false; } dbm.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 4: Has key bool test_has_key() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); if (dbm.has_key("key1")) { dbm.close(); cleanup_dir(temp_dir); return false; // Should not exist } dbm.set("key1", string_to_binary("value1")); if (!dbm.has_key("key1")) { dbm.close(); cleanup_dir(temp_dir); return false; // Should exist } if (dbm.has_key("key2")) { dbm.close(); cleanup_dir(temp_dir); return false; // Should not exist } dbm.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 5: Keys iteration bool test_keys_iteration() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); dbm.set("key1", string_to_binary("value1")); dbm.set("key2", string_to_binary("value2")); dbm.set("key3", string_to_binary("value3")); var keys = dbm.keys; if (keys.count() != 3) { dbm.close(); cleanup_dir(temp_dir); return false; } // Check all keys are present var key_set = new Invercargill.DataStructures.HashSet(); foreach (var key in keys) { key_set.add(key); } if (!key_set.has("key1")) { dbm.close(); cleanup_dir(temp_dir); return false; } if (!key_set.has("key2")) { dbm.close(); cleanup_dir(temp_dir); return false; } if (!key_set.has("key3")) { dbm.close(); cleanup_dir(temp_dir); return false; } dbm.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 6: Transaction commit bool test_transaction_commit() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); dbm.set("key1", string_to_binary("value1")); dbm.begin_transaction(); dbm.set("key2", string_to_binary("value2")); dbm.set("key3", string_to_binary("value3")); // Before commit, values should be in transaction buffer var value2 = dbm.get("key2"); if (value2 == null) { dbm.close(); cleanup_dir(temp_dir); return false; } dbm.commit_transaction(); // After commit, values should be persisted if (!dbm.has_key("key1")) { dbm.close(); cleanup_dir(temp_dir); return false; } if (!dbm.has_key("key2")) { dbm.close(); cleanup_dir(temp_dir); return false; } if (!dbm.has_key("key3")) { dbm.close(); cleanup_dir(temp_dir); return false; } dbm.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 7: Transaction rollback bool test_transaction_rollback() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); dbm.set("key1", string_to_binary("value1")); dbm.begin_transaction(); dbm.set("key2", string_to_binary("value2")); // Value should be visible in transaction if (!dbm.has_key("key2")) { dbm.close(); cleanup_dir(temp_dir); return false; } dbm.rollback_transaction(); // After rollback, key2 should not exist if (dbm.has_key("key2")) { dbm.close(); cleanup_dir(temp_dir); return false; } // key1 should still exist if (!dbm.has_key("key1")) { dbm.close(); cleanup_dir(temp_dir); return false; } dbm.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 8: Transaction with delete bool test_transaction_delete() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); dbm.set("key1", string_to_binary("value1")); dbm.set("key2", string_to_binary("value2")); dbm.begin_transaction(); dbm.delete("key1"); // In transaction, key1 should not be visible if (dbm.has_key("key1")) { dbm.close(); cleanup_dir(temp_dir); return false; } dbm.commit_transaction(); // After commit, key1 should be deleted if (dbm.has_key("key1")) { dbm.close(); cleanup_dir(temp_dir); return false; } // key2 should still exist if (!dbm.has_key("key2")) { dbm.close(); cleanup_dir(temp_dir); return false; } dbm.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 9: Overwrite value bool test_overwrite_value() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); dbm.set("key1", string_to_binary("value1")); var value = dbm.get("key1"); if (value == null || binary_to_string((!) value) != "value1") { dbm.close(); cleanup_dir(temp_dir); return false; } dbm.set("key1", string_to_binary("value2")); value = dbm.get("key1"); if (value == null || binary_to_string((!) value) != "value2") { dbm.close(); cleanup_dir(temp_dir); return false; } dbm.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 10: Special key names bool test_special_key_names() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); string[] special_keys = { "key/with/slashes", "key:with:colons", "key.with.dots", "key with spaces", "key\nwith\nnewlines", "日本語キー" }; foreach (var key in special_keys) { dbm.set(key, string_to_binary(@"value for $key")); var value = dbm.get(key); if (value == null) { dbm.close(); cleanup_dir(temp_dir); return false; } if (binary_to_string((!) value) != @"value for $key") { dbm.close(); cleanup_dir(temp_dir); return false; } } dbm.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 11: Binary data bool test_binary_data() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); // Create binary data with all byte values var data = new uint8[256]; for (int i = 0; i < 256; i++) { data[i] = (uint8) i; } var binary = new Invercargill.DataStructures.ByteBuffer.from_byte_array(data); dbm.set("binary_key", binary); var value = dbm.get("binary_key"); if (value == null) { dbm.close(); cleanup_dir(temp_dir); return false; } var read_data = ((!) value).to_bytes(); if (read_data.length != 256) { dbm.close(); cleanup_dir(temp_dir); return false; } for (int i = 0; i < 256; i++) { if (read_data.get_data()[i] != (uint8) i) { dbm.close(); cleanup_dir(temp_dir); return false; } } dbm.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 12: Empty key bool test_empty_key() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); dbm.set("", string_to_binary("empty_key_value")); var value = dbm.get(""); if (value == null) { dbm.close(); cleanup_dir(temp_dir); return false; } if (binary_to_string((!) value) != "empty_key_value") { dbm.close(); cleanup_dir(temp_dir); return false; } dbm.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 13: Large value bool test_large_value() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); // Create a 1MB value var data = new uint8[1024 * 1024]; for (int i = 0; i < data.length; i++) { data[i] = (uint8) (i % 256); } var binary = new Invercargill.DataStructures.ByteBuffer.from_byte_array(data); dbm.set("large_key", binary); var value = dbm.get("large_key"); if (value == null) { dbm.close(); cleanup_dir(temp_dir); return false; } var read_data = ((!) value).to_bytes(); if (read_data.length != data.length) { dbm.close(); cleanup_dir(temp_dir); return false; } for (int i = 0; i < data.length; i++) { if (read_data.get_data()[i] != data[i]) { dbm.close(); cleanup_dir(temp_dir); return false; } } dbm.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 14: Multiple operations bool test_multiple_operations() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); dbm.open(db_path, false); // Set multiple keys for (int i = 0; i < 100; i++) { dbm.set(@"key$i", string_to_binary(@"value$i")); } // Verify all keys for (int i = 0; i < 100; i++) { var value = dbm.get(@"key$i"); if (value == null) { dbm.close(); cleanup_dir(temp_dir); return false; } if (binary_to_string((!) value) != @"value$i") { dbm.close(); cleanup_dir(temp_dir); return false; } } // Delete half the keys for (int i = 0; i < 50; i++) { dbm.delete(@"key$i"); } // Verify deleted keys are gone for (int i = 0; i < 50; i++) { if (dbm.has_key(@"key$i")) { dbm.close(); cleanup_dir(temp_dir); return false; } } // Verify remaining keys still exist for (int i = 50; i < 100; i++) { if (!dbm.has_key(@"key$i")) { dbm.close(); cleanup_dir(temp_dir); return false; } } dbm.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 15: Persistence bool test_persistence() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { // Create and write var dbm1 = new GdbmDbm(); dbm1.open(db_path, false); dbm1.set("persistent_key", string_to_binary("persistent_value")); dbm1.close(); // Create new instance with same path var dbm2 = new GdbmDbm(); dbm2.open(db_path, false); // Data should persist var value = dbm2.get("persistent_key"); if (value == null) { dbm2.close(); cleanup_dir(temp_dir); return false; } if (binary_to_string((!) value) != "persistent_value") { dbm2.close(); cleanup_dir(temp_dir); return false; } dbm2.close(); cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Test 16: Open and close bool test_open_close() { string db_path = create_temp_db_path(); string temp_dir = get_dir_from_path(db_path); try { var dbm = new GdbmDbm(); if (dbm.is_open) { cleanup_dir(temp_dir); return false; } dbm.open(db_path, false); if (!dbm.is_open) { cleanup_dir(temp_dir); return false; } dbm.close(); if (dbm.is_open) { cleanup_dir(temp_dir); return false; } cleanup_dir(temp_dir); return true; } catch (Error e) { cleanup_dir(temp_dir); return false; } } // Cleanup helper void cleanup_dir(string path) { // Remove all files in directory and the directory itself try { Dir dir = Dir.open(path, 0); string? name; while ((name = dir.read_name()) != null) { string file_path = Path.build_filename(path, name); if (FileUtils.test(file_path, FileTest.IS_DIR)) { cleanup_dir(file_path); } else { FileUtils.unlink(file_path); } } } catch (FileError e) { // Ignore errors } DirUtils.remove(path); }