Skip to content

Commit aaff87b

Browse files
committed
Add unit tests using gtest
1 parent a8e0cc0 commit aaff87b

File tree

2 files changed

+232
-0
lines changed

2 files changed

+232
-0
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "googletest"]
2+
path = googletest
3+
url = https://github.com/google/googletest.git

ConcurrentHashMapTest.cpp

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
#include "ConcurrentHashMap.h"
2+
#include <gtest/gtest.h>
3+
#include <thread>
4+
#include <vector>
5+
#include <atomic>
6+
#include <string>
7+
8+
class ConcurrentHashMapTest : public ::testing::Test
9+
{
10+
protected:
11+
void SetUp() override
12+
{
13+
map_ = std::make_unique<ConcurrentHashMap<std::string, int>>();
14+
}
15+
16+
std::unique_ptr<ConcurrentHashMap<std::string, int>> map_;
17+
};
18+
19+
TEST_F(ConcurrentHashMapTest, BasicInsertAndRetrieve)
20+
{
21+
map_->insert("apple", 10);
22+
auto result = map_->get("apple");
23+
ASSERT_TRUE(result.has_value());
24+
EXPECT_EQ(10, *result);
25+
}
26+
27+
TEST_F(ConcurrentHashMapTest, UpdateExistingKey)
28+
{
29+
map_->insert("apple", 10);
30+
map_->insert("apple", 20);
31+
auto result = map_->get("apple");
32+
ASSERT_TRUE(result.has_value());
33+
EXPECT_EQ(20, *result);
34+
}
35+
36+
TEST_F(ConcurrentHashMapTest, RemoveKey)
37+
{
38+
map_->insert("banana", 30);
39+
map_->remove("banana");
40+
auto result = map_->get("banana");
41+
EXPECT_FALSE(result.has_value());
42+
}
43+
44+
TEST_F(ConcurrentHashMapTest, NonExistentKey)
45+
{
46+
auto result = map_->get("mango");
47+
EXPECT_FALSE(result.has_value());
48+
}
49+
50+
TEST_F(ConcurrentHashMapTest, ConcurrentInserts)
51+
{
52+
constexpr int NUM_THREADS = 8;
53+
constexpr int ITEMS_PER_THREAD = 100;
54+
std::vector<std::thread> threads;
55+
56+
for(int i = 0; i < NUM_THREADS; ++i)
57+
{
58+
threads.emplace_back([this, i]
59+
{
60+
for(int j = 0; j < ITEMS_PER_THREAD; ++j)
61+
{
62+
std::string key = "thread" + std::to_string(i) + "-" + std::to_string(j);
63+
map_->insert(key, j);
64+
}
65+
});
66+
}
67+
68+
for(auto &t : threads)
69+
{
70+
t.join();
71+
}
72+
73+
// Verify all items were inserted
74+
for(int i = 0; i < NUM_THREADS; ++i)
75+
{
76+
for(int j = 0; j < ITEMS_PER_THREAD; ++j)
77+
{
78+
std::string key = "thread" + std::to_string(i) + "-" + std::to_string(j);
79+
auto result = map_->get(key);
80+
ASSERT_TRUE(result.has_value()) << "Missing key: " << key;
81+
EXPECT_EQ(j, *result);
82+
}
83+
}
84+
}
85+
86+
TEST_F(ConcurrentHashMapTest, ConcurrentUpdates)
87+
{
88+
constexpr int NUM_THREADS = 8;
89+
std::vector<std::thread> threads;
90+
std::atomic<bool> start{false};
91+
// Initial value
92+
map_->insert("contended", 0);
93+
94+
for(int i = 0; i < NUM_THREADS; ++i)
95+
{
96+
threads.emplace_back([this, &start, i]
97+
{
98+
while(!start) { /* spin */ } // Wait for start signal
99+
100+
for(int j = 0; j < 100; ++j)
101+
{
102+
map_->insert("contended", i * 100 + j);
103+
}
104+
});
105+
}
106+
107+
start = true;
108+
109+
for(auto &t : threads)
110+
{
111+
t.join();
112+
}
113+
114+
// Verify the final value is from the last writer
115+
auto result = map_->get("contended");
116+
ASSERT_TRUE(result.has_value());
117+
// The exact value depends on thread scheduling, but it should be
118+
// from one of the threads (between 0*100+99 and 7*100+99)
119+
EXPECT_GE(*result, 99);
120+
EXPECT_LE(*result, 799);
121+
}
122+
123+
TEST_F(ConcurrentHashMapTest, ConcurrentReadWrite)
124+
{
125+
constexpr int NUM_WRITERS = 4;
126+
constexpr int NUM_READERS = 4;
127+
std::atomic<bool> running{true};
128+
std::vector<std::thread> writers;
129+
std::vector<std::thread> readers;
130+
131+
// Writers constantly update values
132+
for(int i = 0; i < NUM_WRITERS; ++i)
133+
{
134+
writers.emplace_back([this, i, &running]
135+
{
136+
while(running)
137+
{
138+
map_->insert("key" + std::to_string(i), i);
139+
}
140+
});
141+
}
142+
143+
// Readers constantly read values
144+
for(int i = 0; i < NUM_READERS; ++i)
145+
{
146+
readers.emplace_back([this, &running]
147+
{
148+
while(running)
149+
{
150+
for(int j = 0; j < NUM_WRITERS; ++j)
151+
{
152+
auto result = map_->get("key" + std::to_string(j));
153+
}
154+
}
155+
});
156+
}
157+
158+
// Let them run for 500ms
159+
std::this_thread::sleep_for(std::chrono::milliseconds(500));
160+
running = false;
161+
162+
for(auto &t : writers)
163+
{
164+
t.join();
165+
}
166+
167+
for(auto &t : readers)
168+
{
169+
t.join();
170+
}
171+
172+
// Verify final values are from writers
173+
for(int i = 0; i < NUM_WRITERS; ++i)
174+
{
175+
auto result = map_->get("key" + std::to_string(i));
176+
ASSERT_TRUE(result.has_value());
177+
EXPECT_EQ(i, *result);
178+
}
179+
}
180+
181+
TEST_F(ConcurrentHashMapTest, HighContentionSingleBucket)
182+
{
183+
// Create map with only 1 bucket to maximize contention
184+
ConcurrentHashMap<int, int> singleBucketMap(1);
185+
constexpr int NUM_OPERATIONS = 1000;
186+
std::vector<std::thread> threads;
187+
std::atomic<int> counter{0};
188+
189+
for(int i = 0; i < 8; ++i)
190+
{
191+
threads.emplace_back([&]
192+
{
193+
for(int j = 0; j < NUM_OPERATIONS; ++j)
194+
{
195+
int key = j % 10; // Only 10 different keys
196+
singleBucketMap.insert(key, ++counter);
197+
singleBucketMap.get(key);
198+
199+
if(j % 10 == 0)
200+
{
201+
singleBucketMap.remove(key);
202+
}
203+
}
204+
});
205+
}
206+
207+
for(auto &t : threads)
208+
{
209+
t.join();
210+
}
211+
212+
// Verify final state
213+
for(int i = 0; i < 10; ++i)
214+
{
215+
auto result = singleBucketMap.get(i);
216+
217+
if(result)
218+
{
219+
EXPECT_GT(*result, 0);
220+
EXPECT_LE(*result, counter.load());
221+
}
222+
}
223+
}
224+
225+
int main(int argc, char **argv)
226+
{
227+
::testing::InitGoogleTest(&argc, argv);
228+
return RUN_ALL_TESTS();
229+
}

0 commit comments

Comments
 (0)