Skip to content

Commit adf851e

Browse files
authored
Merge pull request #34 from poyrazK/feat/btree-index-tests
test: add btree_index unit tests (26 tests)
2 parents 7a91cee + 0acd816 commit adf851e

2 files changed

Lines changed: 322 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ if(BUILD_TESTS)
141141
add_cloudsql_test(lexer_tests tests/lexer_tests.cpp)
142142
add_cloudsql_test(parser_tests tests/parser_tests.cpp)
143143
add_cloudsql_test(expression_tests tests/expression_tests.cpp)
144+
add_cloudsql_test(btree_index_tests tests/btree_index_tests.cpp)
144145
add_cloudsql_test(storage_manager_tests tests/storage_manager_tests.cpp)
145146
add_cloudsql_test(rpc_server_tests tests/rpc_server_tests.cpp)
146147
add_cloudsql_test(operator_tests tests/operator_tests.cpp)

tests/btree_index_tests.cpp

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
/**
2+
* @file btree_index_tests.cpp
3+
* @brief Unit tests for BTreeIndex - B+ tree index storage
4+
*/
5+
6+
#include <gtest/gtest.h>
7+
8+
#include <cstdint>
9+
#include <cstdio>
10+
#include <memory>
11+
#include <string>
12+
#include <vector>
13+
14+
#include "common/config.hpp"
15+
#include "common/value.hpp"
16+
#include "storage/btree_index.hpp"
17+
#include "storage/buffer_pool_manager.hpp"
18+
#include "storage/heap_table.hpp"
19+
#include "storage/storage_manager.hpp"
20+
21+
using namespace cloudsql::common;
22+
using namespace cloudsql::storage;
23+
using cloudsql::config::Config;
24+
25+
namespace {
26+
27+
class BTreeIndexTests : public ::testing::Test {
28+
protected:
29+
void SetUp() override {
30+
disk_manager_ = std::make_unique<StorageManager>("./test_idx_data");
31+
disk_manager_->create_dir_if_not_exists();
32+
bpm_ =
33+
std::make_unique<BufferPoolManager>(Config::DEFAULT_BUFFER_POOL_SIZE, *disk_manager_);
34+
35+
index_ = std::make_unique<BTreeIndex>("test_index", *bpm_, ValueType::TYPE_INT64);
36+
}
37+
38+
void TearDown() override {
39+
index_.reset();
40+
bpm_.reset();
41+
disk_manager_.reset();
42+
// Cleanup test files
43+
std::remove("./test_idx_data/test_index.idx");
44+
}
45+
46+
std::unique_ptr<StorageManager> disk_manager_;
47+
std::unique_ptr<BufferPoolManager> bpm_;
48+
std::unique_ptr<BTreeIndex> index_;
49+
};
50+
51+
// Helper to create tuple ids
52+
static HeapTable::TupleId make_rid(uint32_t page, uint16_t slot) {
53+
return HeapTable::TupleId(page, slot);
54+
}
55+
56+
// ============= Constructor Tests =============
57+
58+
TEST_F(BTreeIndexTests, ConstructorBasic) {
59+
EXPECT_NE(index_, nullptr);
60+
EXPECT_EQ(index_->index_name(), "test_index");
61+
EXPECT_EQ(index_->key_type(), ValueType::TYPE_INT64);
62+
}
63+
64+
TEST_F(BTreeIndexTests, ConstructorTextKey) {
65+
auto text_index = std::make_unique<BTreeIndex>("text_idx", *bpm_, ValueType::TYPE_TEXT);
66+
EXPECT_NE(text_index, nullptr);
67+
EXPECT_EQ(text_index->key_type(), ValueType::TYPE_TEXT);
68+
}
69+
70+
// ============= Create/Open/Drop Tests =============
71+
72+
TEST_F(BTreeIndexTests, CreateAndOpen) {
73+
EXPECT_TRUE(index_->create());
74+
EXPECT_TRUE(index_->open());
75+
// Note: drop() may fail if file is still tracked by BPM - test what we can
76+
}
77+
78+
TEST_F(BTreeIndexTests, CreateTwice) {
79+
ASSERT_TRUE(index_->create());
80+
index_->close();
81+
// Second create should succeed
82+
EXPECT_TRUE(index_->create());
83+
}
84+
85+
TEST_F(BTreeIndexTests, OpenWithoutCreate) {
86+
// Should succeed if file already exists from previous test
87+
// (tests share the same test_idx_data directory)
88+
EXPECT_TRUE(index_->open());
89+
}
90+
91+
TEST_F(BTreeIndexTests, DropWithoutCreate) {
92+
// Drop on non-existent file should fail
93+
EXPECT_FALSE(index_->drop());
94+
}
95+
96+
TEST_F(BTreeIndexTests, CreateOpenCloseOpen) {
97+
ASSERT_TRUE(index_->create());
98+
index_->insert(Value::make_int64(42), make_rid(1, 0));
99+
index_->close();
100+
ASSERT_TRUE(index_->open());
101+
auto results = index_->search(Value::make_int64(42));
102+
ASSERT_EQ(results.size(), 1U);
103+
}
104+
105+
// ============= Insert Tests =============
106+
107+
TEST_F(BTreeIndexTests, InsertSingleEntry) {
108+
ASSERT_TRUE(index_->create());
109+
EXPECT_TRUE(index_->open());
110+
111+
auto rid = make_rid(1, 0);
112+
EXPECT_TRUE(index_->insert(Value::make_int64(42), rid));
113+
}
114+
115+
TEST_F(BTreeIndexTests, InsertMultipleEntries) {
116+
ASSERT_TRUE(index_->create());
117+
EXPECT_TRUE(index_->open());
118+
119+
auto rid1 = make_rid(1, 0);
120+
auto rid2 = make_rid(1, 1);
121+
auto rid3 = make_rid(2, 0);
122+
123+
EXPECT_TRUE(index_->insert(Value::make_int64(10), rid1));
124+
EXPECT_TRUE(index_->insert(Value::make_int64(20), rid2));
125+
EXPECT_TRUE(index_->insert(Value::make_int64(30), rid3));
126+
}
127+
128+
TEST_F(BTreeIndexTests, InsertDuplicateKey) {
129+
ASSERT_TRUE(index_->create());
130+
EXPECT_TRUE(index_->open());
131+
132+
auto rid1 = make_rid(1, 0);
133+
auto rid2 = make_rid(1, 1);
134+
135+
EXPECT_TRUE(index_->insert(Value::make_int64(42), rid1));
136+
EXPECT_TRUE(index_->insert(Value::make_int64(42), rid2));
137+
}
138+
139+
TEST_F(BTreeIndexTests, InsertTextKey) {
140+
auto text_index = std::make_unique<BTreeIndex>("text_idx", *bpm_, ValueType::TYPE_TEXT);
141+
ASSERT_TRUE(text_index->create());
142+
ASSERT_TRUE(text_index->open());
143+
144+
auto rid = make_rid(1, 0);
145+
EXPECT_TRUE(text_index->insert(Value::make_text("hello"), rid));
146+
147+
text_index->drop();
148+
}
149+
150+
// ============= Search Tests =============
151+
152+
TEST_F(BTreeIndexTests, SearchExistingKey) {
153+
ASSERT_TRUE(index_->create());
154+
ASSERT_TRUE(index_->open());
155+
156+
auto rid = make_rid(5, 10);
157+
index_->insert(Value::make_int64(42), rid);
158+
159+
auto results = index_->search(Value::make_int64(42));
160+
ASSERT_EQ(results.size(), 1U);
161+
EXPECT_EQ(results[0].page_num, 5U);
162+
EXPECT_EQ(results[0].slot_num, 10U);
163+
}
164+
165+
TEST_F(BTreeIndexTests, SearchNonExistentKey) {
166+
ASSERT_TRUE(index_->create());
167+
ASSERT_TRUE(index_->open());
168+
169+
auto results = index_->search(Value::make_int64(999));
170+
EXPECT_TRUE(results.empty());
171+
}
172+
173+
TEST_F(BTreeIndexTests, SearchMultipleEntries) {
174+
ASSERT_TRUE(index_->create());
175+
ASSERT_TRUE(index_->open());
176+
177+
index_->insert(Value::make_int64(10), make_rid(1, 0));
178+
index_->insert(Value::make_int64(20), make_rid(1, 1));
179+
index_->insert(Value::make_int64(30), make_rid(2, 0));
180+
181+
auto results = index_->search(Value::make_int64(20));
182+
ASSERT_EQ(results.size(), 1U);
183+
EXPECT_EQ(results[0].page_num, 1U);
184+
EXPECT_EQ(results[0].slot_num, 1U);
185+
}
186+
187+
TEST_F(BTreeIndexTests, SearchDuplicateKeys) {
188+
ASSERT_TRUE(index_->create());
189+
ASSERT_TRUE(index_->open());
190+
191+
index_->insert(Value::make_int64(42), make_rid(1, 0));
192+
index_->insert(Value::make_int64(42), make_rid(1, 1));
193+
194+
auto results = index_->search(Value::make_int64(42));
195+
ASSERT_EQ(results.size(), 2U);
196+
}
197+
198+
// ============= Remove Tests =============
199+
200+
TEST_F(BTreeIndexTests, RemoveEntry) {
201+
ASSERT_TRUE(index_->create());
202+
ASSERT_TRUE(index_->open());
203+
204+
index_->insert(Value::make_int64(42), make_rid(1, 0));
205+
206+
// remove() currently just returns true (not implemented)
207+
EXPECT_TRUE(index_->remove(Value::make_int64(42), make_rid(1, 0)));
208+
}
209+
210+
// ============= Scan Iterator Tests =============
211+
212+
TEST_F(BTreeIndexTests, ScanEmptyIndex) {
213+
ASSERT_TRUE(index_->create());
214+
ASSERT_TRUE(index_->open());
215+
216+
auto it = index_->scan();
217+
// Empty index with root at page 0 should not be immediately done
218+
// (iterator starts at root page 0, which may have data or not)
219+
// Just verify we can call is_done without error
220+
EXPECT_FALSE(it.is_done());
221+
}
222+
223+
TEST_F(BTreeIndexTests, ScanSingleEntry) {
224+
ASSERT_TRUE(index_->create());
225+
ASSERT_TRUE(index_->open());
226+
227+
index_->insert(Value::make_int64(42), make_rid(1, 0));
228+
229+
auto it = index_->scan();
230+
EXPECT_FALSE(it.is_done());
231+
232+
BTreeIndex::Entry entry;
233+
EXPECT_TRUE(it.next(entry));
234+
EXPECT_EQ(entry.key.as_int64(), 42);
235+
EXPECT_EQ(entry.tuple_id.page_num, 1U);
236+
EXPECT_EQ(entry.tuple_id.slot_num, 0U);
237+
}
238+
239+
TEST_F(BTreeIndexTests, ScanMultipleEntries) {
240+
ASSERT_TRUE(index_->create());
241+
ASSERT_TRUE(index_->open());
242+
243+
index_->insert(Value::make_int64(10), make_rid(1, 0));
244+
index_->insert(Value::make_int64(20), make_rid(1, 1));
245+
index_->insert(Value::make_int64(30), make_rid(2, 0));
246+
247+
auto it = index_->scan();
248+
int count = 0;
249+
BTreeIndex::Entry entry;
250+
while (it.next(entry)) {
251+
count++;
252+
}
253+
EXPECT_EQ(count, 3);
254+
EXPECT_TRUE(it.is_done());
255+
}
256+
257+
TEST_F(BTreeIndexTests, ScanIteratorIsDoneAfterEnd) {
258+
ASSERT_TRUE(index_->create());
259+
ASSERT_TRUE(index_->open());
260+
261+
index_->insert(Value::make_int64(42), make_rid(1, 0));
262+
263+
auto it = index_->scan();
264+
BTreeIndex::Entry entry;
265+
it.next(entry); // Get the entry
266+
EXPECT_FALSE(it.is_done());
267+
it.next(entry); // Try to get more - should fail
268+
EXPECT_TRUE(it.is_done());
269+
}
270+
271+
// ============= TupleId Tests =============
272+
273+
TEST_F(BTreeIndexTests, TupleIdDefault) {
274+
HeapTable::TupleId rid;
275+
EXPECT_TRUE(rid.is_null());
276+
}
277+
278+
TEST_F(BTreeIndexTests, TupleIdWithValues) {
279+
HeapTable::TupleId rid(5, 10);
280+
EXPECT_EQ(rid.page_num, 5U);
281+
EXPECT_EQ(rid.slot_num, 10U);
282+
EXPECT_FALSE(rid.is_null());
283+
}
284+
285+
// ============= Entry Tests =============
286+
287+
TEST_F(BTreeIndexTests, EntryWithKeyAndTupleId) {
288+
auto key = Value::make_int64(42);
289+
auto rid = make_rid(1, 0);
290+
BTreeIndex::Entry entry(key, rid);
291+
292+
EXPECT_EQ(entry.key.as_int64(), 42);
293+
EXPECT_EQ(entry.tuple_id.page_num, 1U);
294+
EXPECT_EQ(entry.tuple_id.slot_num, 0U);
295+
}
296+
297+
// ============= Index Name Tests =============
298+
299+
TEST_F(BTreeIndexTests, IndexName) {
300+
EXPECT_EQ(index_->index_name(), "test_index");
301+
}
302+
303+
TEST_F(BTreeIndexTests, KeyType) {
304+
EXPECT_EQ(index_->key_type(), ValueType::TYPE_INT64);
305+
}
306+
307+
// ============= Persistence Tests =============
308+
309+
TEST_F(BTreeIndexTests, DataPersistenceAcrossOpenClose) {
310+
ASSERT_TRUE(index_->create());
311+
index_->insert(Value::make_int64(42), make_rid(1, 0));
312+
index_->close();
313+
314+
ASSERT_TRUE(index_->open());
315+
auto results = index_->search(Value::make_int64(42));
316+
ASSERT_EQ(results.size(), 1U);
317+
EXPECT_EQ(results[0].page_num, 1U);
318+
EXPECT_EQ(results[0].slot_num, 0U);
319+
}
320+
321+
} // namespace

0 commit comments

Comments
 (0)