• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }