1 #include <gtest/gtest.h>
2
3 #include "dirent.h"
4
5 #include <codecvt>
6 #include <filesystem>
7 #include <fstream>
8 #include <locale>
9 #include <random>
10
11 namespace fs = std::filesystem;
12
13 // Helper function to create a directory with a specific name
createDirectory(const fs::path & dirName)14 void createDirectory(const fs::path& dirName) { fs::create_directories(dirName); }
15
16 // Helper function to create a file with a specific name
createFile(const fs::path & filename)17 void createFile(const fs::path& filename) {
18 std::ofstream file(filename);
19 file.close();
20 }
21
22 // Helper function to convert UTF-8 to wide string
utf8ToWide(const std::string & utf8Str)23 std::wstring utf8ToWide(const std::string& utf8Str) {
24 std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
25 return converter.from_bytes(utf8Str);
26 }
27
28 // Helper function to convert wide string to UTF-8
wideToUtf8(const std::wstring & wideStr)29 std::string wideToUtf8(const std::wstring& wideStr) {
30 std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
31 return converter.to_bytes(wideStr);
32 }
33
34 class DirentTest : public ::testing::Test {
35 protected:
36 // Setup - create a temporary directory for testing
SetUp()37 void SetUp() override {
38 // Generate a random directory name
39 std::random_device rd;
40 std::mt19937 gen(rd());
41 std::uniform_int_distribution<> dist(0, 15); // For hex characters
42
43 std::string randomDirName = "dirent_test_";
44 for (int i = 0; i < 8; ++i) {
45 randomDirName += "0123456789abcdef"[dist(gen)];
46 }
47
48 tempDir = fs::temp_directory_path() / randomDirName;
49 fs::create_directories(tempDir);
50 }
51
52 // Teardown - remove the temporary directory
TearDown()53 void TearDown() override {
54 try {
55 fs::remove_all(tempDir);
56 } catch (const fs::filesystem_error& e) {
57 std::cerr << "Warning failed to remove directory: " << e.what() << std::endl;
58 }
59 }
60
61 fs::path tempDir;
62 };
63
64 // Test opendir with an invalid directory name
TEST_F(DirentTest,OpenDirInvalid)65 TEST_F(DirentTest, OpenDirInvalid) {
66 DIR* dir = opendir("invalid_dir");
67 ASSERT_EQ(nullptr, dir);
68 ASSERT_EQ(ENOENT, errno);
69 }
70
71 // Test opendir with a valid directory name
TEST_F(DirentTest,OpenDirValid)72 TEST_F(DirentTest, OpenDirValid) {
73 DIR* dir = opendir(tempDir.string().c_str());
74 ASSERT_NE(nullptr, dir);
75 closedir(dir);
76 }
77
78 // Test readdir with an empty directory
TEST_F(DirentTest,ReadDirEmpty)79 TEST_F(DirentTest, ReadDirEmpty) {
80 DIR* dir = opendir(tempDir.string().c_str());
81 ASSERT_NE(nullptr, dir);
82 struct dirent* entry = readdir(dir);
83 ASSERT_EQ(nullptr, entry);
84 closedir(dir);
85 }
86
87 // Test readdir with some files
TEST_F(DirentTest,ReadDirBasic)88 TEST_F(DirentTest, ReadDirBasic) {
89 createFile(tempDir / "file1.txt");
90 createFile(tempDir / "file2.txt");
91
92 DIR* dir = opendir(tempDir.string().c_str());
93 ASSERT_NE(nullptr, dir);
94
95 struct dirent* entry;
96 int count = 0;
97 while ((entry = readdir(dir)) != nullptr) {
98 ASSERT_TRUE(strcmp(entry->d_name, "file1.txt") == 0 ||
99 strcmp(entry->d_name, "file2.txt") == 0);
100 count++;
101 }
102 ASSERT_EQ(2, count);
103
104 closedir(dir);
105 }
106
107 // Test readdir with UTF-8 filenames
TEST_F(DirentTest,ReadDirUtf8)108 TEST_F(DirentTest, ReadDirUtf8) {
109 std::wstring filename = L"hiফাইলhi.txt";
110
111 // We expect a utf8 filename..
112 std::string filenameu8 = wideToUtf8(filename); // u8"hiফাইলhi.txt";
113 createFile(tempDir / filename);
114
115 ASSERT_TRUE(fs::exists(tempDir / filename));
116 DIR* dir = opendir(tempDir.string().c_str());
117 ASSERT_NE(nullptr, dir);
118
119 struct dirent* entry = readdir(dir);
120 ASSERT_NE(nullptr, entry);
121 ASSERT_EQ(filenameu8, entry->d_name);
122
123 closedir(dir);
124 }
125
126 // Test rewinddir
TEST_F(DirentTest,RewindDir)127 TEST_F(DirentTest, RewindDir) {
128 createFile(tempDir / "file1.txt");
129 createFile(tempDir / "file2.txt");
130
131 DIR* dir = opendir(tempDir.string().c_str());
132 ASSERT_NE(nullptr, dir);
133
134 // Read the first entry
135 struct dirent* entry1 = readdir(dir);
136 ASSERT_NE(nullptr, entry1);
137
138 // Rewind the directory
139 rewinddir(dir);
140
141 // Read the first entry again
142 struct dirent* entry2 = readdir(dir);
143 ASSERT_NE(nullptr, entry2);
144 ASSERT_STREQ(entry1->d_name, entry2->d_name);
145
146 closedir(dir);
147 }
148
149 // Test telldir/seekdir (limited functionality)
TEST_F(DirentTest,TellSeekDir)150 TEST_F(DirentTest, TellSeekDir) {
151 createFile(tempDir / "file1.txt");
152 createFile(tempDir / "file2.txt");
153 createFile(tempDir / "file3.txt");
154
155 DIR* dir = opendir(tempDir.string().c_str());
156 ASSERT_NE(nullptr, dir);
157
158 // Get initial position (should be 0)
159 long initialPos = telldir(dir);
160 ASSERT_EQ(0, initialPos);
161
162 // Read the first entry
163 struct dirent* entry1 = readdir(dir);
164 ASSERT_NE(nullptr, entry1);
165
166 // Get position (should be 1 now)
167 long pos1 = telldir(dir);
168 ASSERT_EQ(1, pos1);
169
170 // Read the second entry
171 struct dirent* entry2 = readdir(dir);
172 ASSERT_NE(nullptr, entry2);
173
174 // Get position (should be 2 now)
175 long pos2 = telldir(dir);
176 ASSERT_EQ(2, pos2);
177
178 // Seek to beginning
179 seekdir(dir, 0);
180 long currentPos = telldir(dir);
181 ASSERT_EQ(0, currentPos);
182
183 // Verify we can read again from the beginning
184 struct dirent* entry3 = readdir(dir);
185 ASSERT_NE(nullptr, entry3);
186 ASSERT_STREQ(entry1->d_name, entry3->d_name);
187
188 // Seek to position 1
189 seekdir(dir, 1);
190 currentPos = telldir(dir);
191 ASSERT_EQ(1, currentPos);
192
193 // Verify we can read the second entry again
194 struct dirent* entry4 = readdir(dir);
195 ASSERT_NE(nullptr, entry4);
196 ASSERT_STREQ(entry2->d_name, entry4->d_name);
197
198 // Seek to end
199 seekdir(dir, -1);
200 currentPos = telldir(dir);
201 ASSERT_EQ(-1, currentPos);
202
203 // Check that readdir returns nullptr after seekdir(-1)
204 struct dirent* entry5 = readdir(dir);
205 ASSERT_EQ(nullptr, entry5);
206
207 // Seek to position 2
208 seekdir(dir, 2);
209 currentPos = telldir(dir);
210 ASSERT_EQ(2, currentPos);
211
212 // Read the third entry
213 struct dirent* entry6 = readdir(dir);
214 ASSERT_NE(nullptr, entry6);
215 ASSERT_STREQ("file3.txt", entry6->d_name);
216
217 // Try seeking beyond the end
218 seekdir(dir, 10);
219 currentPos = telldir(dir);
220 ASSERT_EQ(errno, EINVAL); // Bad!
221
222 // Verify that readdir returns nullptr
223 struct dirent* entry7 = readdir(dir);
224 ASSERT_EQ(nullptr, entry7);
225
226 closedir(dir);
227 }
228
229 // Test closedir
TEST_F(DirentTest,CloseDir)230 TEST_F(DirentTest, CloseDir) {
231 DIR* dir = opendir(tempDir.string().c_str());
232 ASSERT_NE(nullptr, dir);
233 int result = closedir(dir);
234 ASSERT_EQ(0, result);
235 }
236
237 // Test extended path
TEST_F(DirentTest,ExtendedPath)238 TEST_F(DirentTest, ExtendedPath) {
239 // Create a path that exceeds MAX_PATH
240 std::wstring longDirName = L"\\\\?\\" + tempDir.wstring() + L"\\long_directory_name";
241 for (int i = 0; i < 30; ++i) {
242 longDirName += L"\\subdir";
243 }
244
245 // Create the long directory structure
246 ASSERT_TRUE(fs::create_directories(longDirName));
247
248 // Create a file within the long directory
249 std::wstring longFileName = longDirName + L"\\file.txt";
250 std::ofstream file(longFileName);
251 ASSERT_TRUE(file.is_open());
252 file.close();
253
254 // Convert to UTF-8 for opendir
255 std::string longDirNameUtf8 = wideToUtf8(longDirName);
256
257 // Open the directory using opendir
258 DIR* dir = opendir(longDirNameUtf8.c_str());
259 ASSERT_NE(nullptr, dir);
260
261 // Read directory entries
262 struct dirent* entry;
263 bool found = false;
264 while ((entry = readdir(dir)) != nullptr) {
265 if (strcmp(entry->d_name, "file.txt") == 0) {
266 found = true;
267 break;
268 }
269 }
270
271 // Check if the file was found
272 ASSERT_TRUE(found);
273
274 // Close the directory
275 closedir(dir);
276
277 // Cleanup
278 fs::remove(longFileName);
279 ASSERT_FALSE(fs::exists(longFileName));
280 }
281
282 // Test various error conditions
TEST_F(DirentTest,ErrorConditions)283 TEST_F(DirentTest, ErrorConditions) {
284 // Invalid directory name
285 DIR* dir = opendir(nullptr);
286 ASSERT_EQ(nullptr, dir);
287 ASSERT_EQ(EINVAL, errno);
288
289 // Directory not found
290 dir = opendir("nonexistent_directory");
291 ASSERT_EQ(nullptr, dir);
292 ASSERT_EQ(ENOENT, errno);
293
294 // Not a directory
295 createFile(tempDir / "file.txt");
296 dir = opendir((tempDir / "file.txt").c_str());
297 ASSERT_EQ(nullptr, dir);
298 ASSERT_EQ(ENOTDIR, errno);
299
300 // Invalid DIR pointer
301 struct dirent* entry = readdir(nullptr);
302 ASSERT_EQ(nullptr, entry);
303 ASSERT_EQ(EBADF, errno);
304
305 int result = closedir(nullptr);
306 ASSERT_EQ(-1, result);
307 ASSERT_EQ(EBADF, errno);
308
309 rewinddir(nullptr);
310 ASSERT_EQ(EBADF, errno);
311
312 seekdir(nullptr, 0);
313 ASSERT_EQ(EBADF, errno);
314
315 long pos = telldir(nullptr);
316 ASSERT_EQ(-1, pos);
317 ASSERT_EQ(EBADF, errno);
318 }